@canonry/canonry 7.24.1 → 7.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +1 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +21 -2
- package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +1 -0
- package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +3 -2
- package/assets/assets/{AuditHistoryPanel-DK3zE9dO.js → AuditHistoryPanel-cQyVA85P.js} +1 -1
- package/assets/assets/{BacklinksPage-WYFDcXpX.js → BacklinksPage-UGZ_9N7F.js} +1 -1
- package/assets/assets/{HistoryPage-CSlndAeS.js → HistoryPage-UJVFZ5LK.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-CivoZOGQ.js → MeasurementPropertyPage-ChpOxQfx.js} +1 -1
- package/assets/assets/{ProjectPage-zidnYv65.js → ProjectPage-CvxuPSiC.js} +1 -1
- package/assets/assets/{RunsPage-BGMAZ_mA.js → RunsPage-C6U_YcDY.js} +1 -1
- package/assets/assets/{SettingsPage-ndSVVJcE.js → SettingsPage-DeKxjuTr.js} +1 -1
- package/assets/assets/{SiteHealthSection-C7-RrsKk.js → SiteHealthSection-COeVsgop.js} +3 -3
- package/assets/assets/{TrafficPage-2ANBE9Lc.js → TrafficPage-DmMmeJKK.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-CLYVB4Uq.js → TrafficSourceDetailPage-CUehseZe.js} +1 -1
- package/assets/assets/{extract-error-message-BzPoRYiA.js → extract-error-message-BsxdS7Po.js} +1 -1
- package/assets/assets/{index-CjowCLs3.js → index-ahEr6Oc-.js} +19 -19
- package/assets/assets/{react-sigma_core.esm.min-BmKqjMGN.js → react-sigma_core.esm.min--MbbBWzJ.js} +1 -1
- package/assets/assets/{v2-overview-adapter-C7eI2iBf.js → v2-overview-adapter-D-IScpdp.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-43FZZFOK.js → chunk-2C6NWFN4.js} +2 -2
- package/dist/{chunk-656KW4RB.js → chunk-42XUY243.js} +28 -5
- package/dist/{chunk-OAWFZAAJ.js → chunk-FW4GX2LO.js} +3 -3
- package/dist/{chunk-35IRQYDW.js → chunk-K5EGSSXC.js} +145 -25
- package/dist/{chunk-5ITYNLS3.js → chunk-QFXD7GR5.js} +4 -3
- package/dist/{chunk-H7UNFLYH.js → chunk-WRHA54BN.js} +794 -619
- package/dist/{chunk-332IBSJJ.js → chunk-ZIETXJYU.js} +6 -4
- package/dist/cli.js +40 -17
- package/dist/{demo-server-NLD6B4WH.js → demo-server-G5OK7SWE.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-JFHM2EAY.js → intelligence-service-3ZUVTIO7.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +10 -10
|
@@ -864,10 +864,10 @@ import {
|
|
|
864
864
|
wordpressSchemaDeployResultDtoSchema,
|
|
865
865
|
wordpressSchemaStatusResultDtoSchema,
|
|
866
866
|
wordpressStatusDtoSchema
|
|
867
|
-
} from "./chunk-
|
|
867
|
+
} from "./chunk-42XUY243.js";
|
|
868
868
|
|
|
869
869
|
// src/intelligence-service.ts
|
|
870
|
-
import { eq as
|
|
870
|
+
import { eq as eq90, desc as desc42, asc as asc18, and as and76, ne as ne16, or as or23, inArray as inArray37, gte as gte24, lte as lte20, isNull as isNull17, sql as sql32, exists as exists2 } from "drizzle-orm";
|
|
871
871
|
|
|
872
872
|
// ../db/src/client.ts
|
|
873
873
|
import { mkdirSync } from "fs";
|
|
@@ -1771,7 +1771,34 @@ var doctorHealthState = sqliteTable("doctor_health_state", {
|
|
|
1771
1771
|
* as changed, so the trigger falls back to the code rule for one pass rather
|
|
1772
1772
|
* than paging every already-degraded project the moment this ships.
|
|
1773
1773
|
*/
|
|
1774
|
-
failingSignature: text("failing_signature")
|
|
1774
|
+
failingSignature: text("failing_signature"),
|
|
1775
|
+
/**
|
|
1776
|
+
* The same set with each entry's check id: `status:code:id`, worst first,
|
|
1777
|
+
* with `warn:health.no-signal:health.no-signal` standing in for a pass where
|
|
1778
|
+
* no check could be graded. A check changes its code when its severity
|
|
1779
|
+
* changes, so only the id can tell a check that eased (fail to warn) from
|
|
1780
|
+
* one that cleared while another started. Kept beside `failingSignature`
|
|
1781
|
+
* rather than in it: a rolled-back release compares that column as a string
|
|
1782
|
+
* and would page every degraded project if its format changed. That release
|
|
1783
|
+
* leaves this column behind, so the notifier trusts it only while its
|
|
1784
|
+
* entries still spell `failingSignature`, and otherwise compares by code for
|
|
1785
|
+
* one pass. NULL on rows written before this column existed.
|
|
1786
|
+
*/
|
|
1787
|
+
failingChecks: text("failing_checks"),
|
|
1788
|
+
/**
|
|
1789
|
+
* What the operator has been told is failing, in the `failingChecks` form:
|
|
1790
|
+
* the set reported by the last health alert a webhook accepted. A pass that
|
|
1791
|
+
* only clears checks sends nothing and still advances `failingChecks`, so
|
|
1792
|
+
* the next alert compares against this to list what cleared since the alert
|
|
1793
|
+
* the operator last saw. Delivery retry state lives in `pendingSignature`:
|
|
1794
|
+
* an earlier delivered set cannot acknowledge a later recurrence. NULL
|
|
1795
|
+
* while no alert has been delivered since this column exists, and reset to
|
|
1796
|
+
* NULL when an older release has written the row since; the notifier then
|
|
1797
|
+
* compares against the previous pass alone.
|
|
1798
|
+
*/
|
|
1799
|
+
notifiedSignature: text("notified_signature"),
|
|
1800
|
+
/** Triggering breaches owed by an unaccepted degraded alert, as `status:code:id`. */
|
|
1801
|
+
pendingSignature: text("pending_signature")
|
|
1775
1802
|
});
|
|
1776
1803
|
var siteLivenessState = sqliteTable("site_liveness_state", {
|
|
1777
1804
|
projectId: text("project_id").primaryKey(),
|
|
@@ -8511,6 +8538,44 @@ var MIGRATION_VERSIONS = [
|
|
|
8511
8538
|
`CREATE INDEX IF NOT EXISTS idx_ga_search_landing_pages_order ON ga_search_landing_pages(project_id, window_key, clicks)`,
|
|
8512
8539
|
`CREATE INDEX IF NOT EXISTS idx_ga_search_landing_pages_run ON ga_search_landing_pages(sync_run_id)`
|
|
8513
8540
|
]
|
|
8541
|
+
},
|
|
8542
|
+
{
|
|
8543
|
+
// The failing set the last DELIVERED health alert reported. A pass that
|
|
8544
|
+
// only clears checks now sends nothing, so the stored signature moves on
|
|
8545
|
+
// without the operator hearing about it; the next alert reads this to say
|
|
8546
|
+
// what cleared since the one they last saw, and an alert no webhook
|
|
8547
|
+
// accepted is sent again. Nullable with no default: existing rows, and
|
|
8548
|
+
// rows an older writer keeps updating, read as "no alert on record" and
|
|
8549
|
+
// fall back to the previous pass.
|
|
8550
|
+
version: 177,
|
|
8551
|
+
name: "doctor-health-notified-signature",
|
|
8552
|
+
statements: [
|
|
8553
|
+
`ALTER TABLE doctor_health_state ADD COLUMN notified_signature TEXT`
|
|
8554
|
+
]
|
|
8555
|
+
},
|
|
8556
|
+
{
|
|
8557
|
+
// The last pass's failing set with check ids (`status:code:id`). A check
|
|
8558
|
+
// changes its code when its severity changes, so without the id a check
|
|
8559
|
+
// easing from fail to warn read as one entry cleared and another gained,
|
|
8560
|
+
// and paged. It sits beside `failing_signature` instead of changing that
|
|
8561
|
+
// column's format: an older binary compares it as a string, and would page
|
|
8562
|
+
// every degraded project after a rollback. Nullable with no default: rows
|
|
8563
|
+
// without it are compared by code for one pass.
|
|
8564
|
+
version: 178,
|
|
8565
|
+
name: "doctor-health-failing-checks",
|
|
8566
|
+
statements: [
|
|
8567
|
+
`ALTER TABLE doctor_health_state ADD COLUMN failing_checks TEXT`
|
|
8568
|
+
]
|
|
8569
|
+
},
|
|
8570
|
+
{
|
|
8571
|
+
// Delivery debt is separate from the last delivered clearance baseline:
|
|
8572
|
+
// a check can clear quietly and recur with a code that baseline knows.
|
|
8573
|
+
// NULL preserves existing rows without inventing an undelivered alert.
|
|
8574
|
+
version: 179,
|
|
8575
|
+
name: "doctor-health-pending-signature",
|
|
8576
|
+
statements: [
|
|
8577
|
+
`ALTER TABLE doctor_health_state ADD COLUMN pending_signature TEXT`
|
|
8578
|
+
]
|
|
8514
8579
|
}
|
|
8515
8580
|
];
|
|
8516
8581
|
var AERO_LEDGER_REBUILDS = [
|
|
@@ -15903,6 +15968,8 @@ var AlertFieldLabels = {
|
|
|
15903
15968
|
check: "Check",
|
|
15904
15969
|
previously: "Was",
|
|
15905
15970
|
remediation: "What to do",
|
|
15971
|
+
newlyFailing: "New",
|
|
15972
|
+
cleared: "Cleared",
|
|
15906
15973
|
alsoFailing: "Also failing",
|
|
15907
15974
|
event: "Event",
|
|
15908
15975
|
run: "Run",
|
|
@@ -15940,7 +16007,24 @@ function healthView(payload) {
|
|
|
15940
16007
|
if (health.remediation) {
|
|
15941
16008
|
fields.push({ label: AlertFieldLabels.remediation, value: health.remediation });
|
|
15942
16009
|
}
|
|
15943
|
-
const
|
|
16010
|
+
const added = health.added ?? [];
|
|
16011
|
+
if (added.length > 0) {
|
|
16012
|
+
const statusOf = /* @__PURE__ */ new Map();
|
|
16013
|
+
for (const check2 of health.failing) if (!statusOf.has(check2.code)) statusOf.set(check2.code, check2.status);
|
|
16014
|
+
fields.push({
|
|
16015
|
+
label: `${AlertFieldLabels.newlyFailing} (${added.length})`,
|
|
16016
|
+
value: bulletList(added.map((code) => `${statusOf.get(code) ?? health.status}: ${code}`))
|
|
16017
|
+
});
|
|
16018
|
+
}
|
|
16019
|
+
const cleared = health.cleared ?? [];
|
|
16020
|
+
if (cleared.length > 0) {
|
|
16021
|
+
fields.push({
|
|
16022
|
+
label: `${AlertFieldLabels.cleared} (${cleared.length})`,
|
|
16023
|
+
value: bulletList(cleared)
|
|
16024
|
+
});
|
|
16025
|
+
}
|
|
16026
|
+
const newlyFailing = new Set(added);
|
|
16027
|
+
const others = health.failing.filter((check2) => check2.code !== health.code && !newlyFailing.has(check2.code));
|
|
15944
16028
|
if (others.length > 0) {
|
|
15945
16029
|
fields.push({
|
|
15946
16030
|
label: `${AlertFieldLabels.alsoFailing} (${others.length})`,
|
|
@@ -19120,6 +19204,34 @@ function aliasArraysEqual(a, b) {
|
|
|
19120
19204
|
import crypto14 from "crypto";
|
|
19121
19205
|
import { and as and15, eq as eq21, inArray as inArray9, sql as sql8 } from "drizzle-orm";
|
|
19122
19206
|
|
|
19207
|
+
// ../api-routes/src/query-class-read.ts
|
|
19208
|
+
function storedQueryClasses(db, projectId) {
|
|
19209
|
+
const classes = /* @__PURE__ */ new Map();
|
|
19210
|
+
let plan;
|
|
19211
|
+
try {
|
|
19212
|
+
const version = activePlanVersionRow(db, projectId);
|
|
19213
|
+
if (!version) return null;
|
|
19214
|
+
plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
|
|
19215
|
+
} catch {
|
|
19216
|
+
return classes;
|
|
19217
|
+
}
|
|
19218
|
+
if (plan.schemaVersion !== 2) return null;
|
|
19219
|
+
for (const assignment of plan.assignments) {
|
|
19220
|
+
const seen2 = classes.get(assignment.queryId);
|
|
19221
|
+
classes.set(
|
|
19222
|
+
assignment.queryId,
|
|
19223
|
+
seen2 === void 0 || seen2 === assignment.queryClass ? assignment.queryClass : null
|
|
19224
|
+
);
|
|
19225
|
+
}
|
|
19226
|
+
return classes;
|
|
19227
|
+
}
|
|
19228
|
+
function readQueryClasses(db, project, rows) {
|
|
19229
|
+
const stored = storedQueryClasses(db, project.id);
|
|
19230
|
+
if (stored !== null) return new Map(rows.map((row) => [row.id, stored.get(row.id) ?? null]));
|
|
19231
|
+
const classifier = compileQueryClassifier(effectiveBrandNames(project));
|
|
19232
|
+
return new Map(rows.map((row) => [row.id, classifier?.classify(row.query) ?? null]));
|
|
19233
|
+
}
|
|
19234
|
+
|
|
19123
19235
|
// ../api-routes/src/query-replace.ts
|
|
19124
19236
|
import crypto13 from "crypto";
|
|
19125
19237
|
import { and as and14, eq as eq20, inArray as inArray8, isNull as isNull5 } from "drizzle-orm";
|
|
@@ -19287,11 +19399,15 @@ function generationFailure(err, fallbackMessage) {
|
|
|
19287
19399
|
return internalError(message);
|
|
19288
19400
|
}
|
|
19289
19401
|
}
|
|
19402
|
+
function projectQueryDtos(db, project) {
|
|
19403
|
+
const rows = db.select().from(queries).where(eq21(queries.projectId, project.id)).all();
|
|
19404
|
+
const classes = readQueryClasses(db, project, rows);
|
|
19405
|
+
return rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt, queryClass: classes.get(r.id) ?? null }));
|
|
19406
|
+
}
|
|
19290
19407
|
async function queryRoutes(app, opts) {
|
|
19291
19408
|
app.get("/projects/:name/queries", async (request, reply) => {
|
|
19292
19409
|
const project = resolveProject(app.db, request.params.name);
|
|
19293
|
-
|
|
19294
|
-
return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
|
|
19410
|
+
return reply.send(projectQueryDtos(app.db, project));
|
|
19295
19411
|
});
|
|
19296
19412
|
app.put("/projects/:name/queries", async (request, reply) => {
|
|
19297
19413
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -19311,8 +19427,7 @@ async function queryRoutes(app, opts) {
|
|
|
19311
19427
|
diff: { queries: body.queries }
|
|
19312
19428
|
}));
|
|
19313
19429
|
}, { behavior: "immediate" });
|
|
19314
|
-
|
|
19315
|
-
return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
|
|
19430
|
+
return reply.send(projectQueryDtos(app.db, project));
|
|
19316
19431
|
});
|
|
19317
19432
|
app.post("/projects/:name/queries/replace-preview", async (request, reply) => {
|
|
19318
19433
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -19381,8 +19496,7 @@ async function queryRoutes(app, opts) {
|
|
|
19381
19496
|
}));
|
|
19382
19497
|
}
|
|
19383
19498
|
}, { behavior: "immediate" });
|
|
19384
|
-
|
|
19385
|
-
return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
|
|
19499
|
+
return reply.send(projectQueryDtos(app.db, project));
|
|
19386
19500
|
});
|
|
19387
19501
|
app.delete("/projects/:name/queries/:id", async (request, reply) => {
|
|
19388
19502
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -19439,8 +19553,7 @@ async function queryRoutes(app, opts) {
|
|
|
19439
19553
|
diff: { added }
|
|
19440
19554
|
}));
|
|
19441
19555
|
}
|
|
19442
|
-
|
|
19443
|
-
return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
|
|
19556
|
+
return reply.send(projectQueryDtos(app.db, project));
|
|
19444
19557
|
});
|
|
19445
19558
|
app.post("/projects/:name/queries/generate", async (request, reply) => {
|
|
19446
19559
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -23727,8 +23840,7 @@ var MEASUREMENT_PLAN_V2_MAX_BYTES = 4 * 1024 * 1024;
|
|
|
23727
23840
|
var CHECKSUM_PLACEHOLDER = "0".repeat(64);
|
|
23728
23841
|
var MEASUREMENT_V2_PROVENANCE_EPOCH = "1970-01-01T00:00:00.000Z";
|
|
23729
23842
|
function proposeQueryClass(queryText, brandNames) {
|
|
23730
|
-
|
|
23731
|
-
return matcherMatchesText(matcher, queryText) ? "branded" : "non-brand";
|
|
23843
|
+
return compileQueryClassifier(brandNames)?.classify(queryText) ?? "non-brand";
|
|
23732
23844
|
}
|
|
23733
23845
|
function proposeQueryClassForTarget(queryText, brandNames, target) {
|
|
23734
23846
|
const names = target ? [...brandNames, target.label, ...target.aliases] : brandNames;
|
|
@@ -62964,7 +63076,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
62964
63076
|
|
|
62965
63077
|
// ../api-routes/src/backlinks.ts
|
|
62966
63078
|
import crypto45 from "crypto";
|
|
62967
|
-
import { and as
|
|
63079
|
+
import { and as and57, asc as asc14, desc as desc34, eq as eq66, sql as sql25 } from "drizzle-orm";
|
|
62968
63080
|
|
|
62969
63081
|
// ../integration-commoncrawl/src/constants.ts
|
|
62970
63082
|
import os2 from "os";
|
|
@@ -63395,6 +63507,22 @@ function backlinkCrawlerExclusionClause() {
|
|
|
63395
63507
|
return combined;
|
|
63396
63508
|
}
|
|
63397
63509
|
|
|
63510
|
+
// ../api-routes/src/backlinks-source-state.ts
|
|
63511
|
+
import { and as and56, eq as eq65, isNotNull as isNotNull8, or as or17 } from "drizzle-orm";
|
|
63512
|
+
function readCommonCrawlSourceState(db, projectId) {
|
|
63513
|
+
const completedReleases = db.select({ status: ccReleaseSyncs.status }).from(ccReleaseSyncs).where(or17(
|
|
63514
|
+
eq65(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready),
|
|
63515
|
+
isNotNull8(ccReleaseSyncs.queryFinishedAt)
|
|
63516
|
+
)).all();
|
|
63517
|
+
const hasReadyRelease = completedReleases.length > 0;
|
|
63518
|
+
const hasCurrentReadyRelease = completedReleases.some((release) => release.status === CcReleaseSyncStatuses.ready);
|
|
63519
|
+
const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and56(
|
|
63520
|
+
eq65(backlinkSummaries.projectId, projectId),
|
|
63521
|
+
eq65(backlinkSummaries.source, BacklinkSources.commoncrawl)
|
|
63522
|
+
)).limit(1).get() !== void 0;
|
|
63523
|
+
return { hasReadyRelease, hasCurrentReadyRelease, hasProjectData, connected: hasReadyRelease && hasProjectData };
|
|
63524
|
+
}
|
|
63525
|
+
|
|
63398
63526
|
// ../api-routes/src/backlinks.ts
|
|
63399
63527
|
var BACKLINKS_UNSUPPORTED_MESSAGE = "Backlinks sync and install are only available from a local canonry install. Run `canonry backlinks install` locally to use this feature.";
|
|
63400
63528
|
var NON_TERMINAL_SYNC_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -63461,11 +63589,11 @@ function mapRunRow(row) {
|
|
|
63461
63589
|
}
|
|
63462
63590
|
function latestSummaryForProject(db, projectId, source, release) {
|
|
63463
63591
|
const conditions = [
|
|
63464
|
-
|
|
63465
|
-
|
|
63592
|
+
eq66(backlinkSummaries.projectId, projectId),
|
|
63593
|
+
eq66(backlinkSummaries.source, source)
|
|
63466
63594
|
];
|
|
63467
|
-
if (release) conditions.push(
|
|
63468
|
-
return db.select().from(backlinkSummaries).where(
|
|
63595
|
+
if (release) conditions.push(eq66(backlinkSummaries.release, release));
|
|
63596
|
+
return db.select().from(backlinkSummaries).where(and57(...conditions)).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
|
|
63469
63597
|
}
|
|
63470
63598
|
function parseExcludeCrawlers(value) {
|
|
63471
63599
|
if (!value) return false;
|
|
@@ -63473,12 +63601,12 @@ function parseExcludeCrawlers(value) {
|
|
|
63473
63601
|
return lower === "1" || lower === "true" || lower === "yes";
|
|
63474
63602
|
}
|
|
63475
63603
|
function computeFilteredSummary(db, base) {
|
|
63476
|
-
const baseDomainCondition =
|
|
63477
|
-
|
|
63478
|
-
|
|
63479
|
-
|
|
63604
|
+
const baseDomainCondition = and57(
|
|
63605
|
+
eq66(backlinkDomains.projectId, base.projectId),
|
|
63606
|
+
eq66(backlinkDomains.source, base.source),
|
|
63607
|
+
eq66(backlinkDomains.release, base.release)
|
|
63480
63608
|
);
|
|
63481
|
-
const filteredCondition =
|
|
63609
|
+
const filteredCondition = and57(baseDomainCondition, backlinkCrawlerExclusionClause());
|
|
63482
63610
|
const unfilteredAgg = db.select({
|
|
63483
63611
|
count: sql25`count(*)`,
|
|
63484
63612
|
total: sql25`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
@@ -63508,13 +63636,13 @@ function computeFilteredSummary(db, base) {
|
|
|
63508
63636
|
};
|
|
63509
63637
|
}
|
|
63510
63638
|
function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
|
|
63511
|
-
const summary3 = db.select().from(backlinkSummaries).where(
|
|
63639
|
+
const summary3 = db.select().from(backlinkSummaries).where(and57(eq66(backlinkSummaries.projectId, projectId), eq66(backlinkSummaries.source, source))).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
|
|
63512
63640
|
let totalLinkingDomains = summary3?.totalLinkingDomains ?? 0;
|
|
63513
63641
|
if (summary3 && excludeCrawlers) {
|
|
63514
|
-
const filtered = db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(
|
|
63515
|
-
|
|
63516
|
-
|
|
63517
|
-
|
|
63642
|
+
const filtered = db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(and57(
|
|
63643
|
+
eq66(backlinkDomains.projectId, projectId),
|
|
63644
|
+
eq66(backlinkDomains.source, source),
|
|
63645
|
+
eq66(backlinkDomains.release, summary3.release),
|
|
63518
63646
|
backlinkCrawlerExclusionClause()
|
|
63519
63647
|
)).get();
|
|
63520
63648
|
totalLinkingDomains = Number(filtered?.count ?? 0);
|
|
@@ -63529,8 +63657,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
|
|
|
63529
63657
|
};
|
|
63530
63658
|
}
|
|
63531
63659
|
function computeSourceAvailability(db, project, excludeCrawlers) {
|
|
63532
|
-
const
|
|
63533
|
-
const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
|
|
63660
|
+
const ccConnected = readCommonCrawlSourceState(db, project.id).connected;
|
|
63534
63661
|
const sources = [
|
|
63535
63662
|
buildSourceAvailability(db, project.id, BacklinkSources.commoncrawl, ccConnected, excludeCrawlers),
|
|
63536
63663
|
buildSourceAvailability(db, project.id, BacklinkSources["bing-webmaster"], false, excludeCrawlers)
|
|
@@ -63585,7 +63712,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63585
63712
|
"@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
|
|
63586
63713
|
);
|
|
63587
63714
|
}
|
|
63588
|
-
const existing = app.db.select().from(ccReleaseSyncs).where(
|
|
63715
|
+
const existing = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.release, release)).get();
|
|
63589
63716
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
63590
63717
|
if (existing) {
|
|
63591
63718
|
if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
|
|
@@ -63596,9 +63723,9 @@ async function backlinksRoutes(app, opts) {
|
|
|
63596
63723
|
phaseDetail: null,
|
|
63597
63724
|
error: null,
|
|
63598
63725
|
updatedAt: now
|
|
63599
|
-
}).where(
|
|
63726
|
+
}).where(eq66(ccReleaseSyncs.id, existing.id)).run();
|
|
63600
63727
|
opts.onReleaseSyncRequested(existing.id, release);
|
|
63601
|
-
const refreshed = app.db.select().from(ccReleaseSyncs).where(
|
|
63728
|
+
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.id, existing.id)).get();
|
|
63602
63729
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
63603
63730
|
}
|
|
63604
63731
|
const id = crypto45.randomUUID();
|
|
@@ -63610,7 +63737,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63610
63737
|
updatedAt: now
|
|
63611
63738
|
}).run();
|
|
63612
63739
|
opts.onReleaseSyncRequested(id, release);
|
|
63613
|
-
const inserted = app.db.select().from(ccReleaseSyncs).where(
|
|
63740
|
+
const inserted = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.id, id)).get();
|
|
63614
63741
|
return reply.status(201).send(mapSyncRow(inserted));
|
|
63615
63742
|
});
|
|
63616
63743
|
app.get("/backlinks/syncs/latest", async (_request, reply) => {
|
|
@@ -63668,7 +63795,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63668
63795
|
createdAt: now
|
|
63669
63796
|
}).run();
|
|
63670
63797
|
opts.onBacklinkExtractRequested(runId, project.id, release);
|
|
63671
|
-
const run = app.db.select().from(runs).where(
|
|
63798
|
+
const run = app.db.select().from(runs).where(eq66(runs.id, runId)).get();
|
|
63672
63799
|
return reply.status(201).send(mapRunRow(run));
|
|
63673
63800
|
});
|
|
63674
63801
|
app.get(
|
|
@@ -63694,12 +63821,12 @@ async function backlinksRoutes(app, opts) {
|
|
|
63694
63821
|
const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
|
|
63695
63822
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
63696
63823
|
const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
|
|
63697
|
-
const baseDomainCondition =
|
|
63698
|
-
|
|
63699
|
-
|
|
63700
|
-
|
|
63824
|
+
const baseDomainCondition = and57(
|
|
63825
|
+
eq66(backlinkDomains.projectId, project.id),
|
|
63826
|
+
eq66(backlinkDomains.source, source),
|
|
63827
|
+
eq66(backlinkDomains.release, targetRelease)
|
|
63701
63828
|
);
|
|
63702
|
-
const domainCondition = excludeCrawlers ?
|
|
63829
|
+
const domainCondition = excludeCrawlers ? and57(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
|
|
63703
63830
|
const totalRow = app.db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
63704
63831
|
const rows = app.db.select({
|
|
63705
63832
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
@@ -63723,7 +63850,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63723
63850
|
async (request, reply) => {
|
|
63724
63851
|
const project = resolveProject(app.db, request.params.name);
|
|
63725
63852
|
const source = parseSourceParam(request.query.source);
|
|
63726
|
-
const rows = app.db.select().from(backlinkSummaries).where(
|
|
63853
|
+
const rows = app.db.select().from(backlinkSummaries).where(and57(eq66(backlinkSummaries.projectId, project.id), eq66(backlinkSummaries.source, source))).orderBy(asc14(backlinkSummaries.queriedAt)).all();
|
|
63727
63854
|
const response = rows.map((r) => ({
|
|
63728
63855
|
release: r.release,
|
|
63729
63856
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -63750,7 +63877,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63750
63877
|
import crypto48 from "crypto";
|
|
63751
63878
|
import { isIP } from "net";
|
|
63752
63879
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
63753
|
-
import { and as
|
|
63880
|
+
import { and as and61, desc as desc35, eq as eq70, gte as gte16, inArray as inArray28, lte as lte17, sql as sql28 } from "drizzle-orm";
|
|
63754
63881
|
|
|
63755
63882
|
// ../api-routes/src/cloudflare-worker-version.ts
|
|
63756
63883
|
var CURRENT_CLOUDFLARE_WORKER_VERSION = "1.0.1";
|
|
@@ -80416,10 +80543,10 @@ async function ackCloudflareQueueMessages(client, options) {
|
|
|
80416
80543
|
|
|
80417
80544
|
// ../api-routes/src/traffic-event-ingest.ts
|
|
80418
80545
|
import crypto47 from "crypto";
|
|
80419
|
-
import { and as
|
|
80546
|
+
import { and as and59, eq as eq68, gte as gte15, lt as lt12, lte as lte15, sql as sql27 } from "drizzle-orm";
|
|
80420
80547
|
|
|
80421
80548
|
// ../api-routes/src/raw-event-sample-retention.ts
|
|
80422
|
-
import { and as
|
|
80549
|
+
import { and as and58, eq as eq67, lt as lt11, sql as sql26 } from "drizzle-orm";
|
|
80423
80550
|
var RAW_EVENT_SAMPLE_RETENTION_MS = 30 * 24 * 60 * 6e4;
|
|
80424
80551
|
var RAW_EVENT_SAMPLE_RETENTION_SWEEP_INTERVAL_MS = 24 * 60 * 6e4;
|
|
80425
80552
|
var CANONICAL_UTC_TIMESTAMP_GLOB = "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]Z";
|
|
@@ -80429,14 +80556,14 @@ function rawEventSampleRetentionCutoff(referenceAt) {
|
|
|
80429
80556
|
return new Date(referenceMs - RAW_EVENT_SAMPLE_RETENTION_MS).toISOString();
|
|
80430
80557
|
}
|
|
80431
80558
|
function deleteExpiredRawEventSamples(db, cutoff, sourceId) {
|
|
80432
|
-
const sourceScope = sourceId === void 0 ? void 0 :
|
|
80433
|
-
db.delete(rawEventSamples).where(
|
|
80434
|
-
db.delete(rawEventSamples).where(
|
|
80559
|
+
const sourceScope = sourceId === void 0 ? void 0 : eq67(rawEventSamples.sourceId, sourceId);
|
|
80560
|
+
db.delete(rawEventSamples).where(and58(sourceScope, sql26`julianday(${rawEventSamples.ts}) IS NULL`)).run();
|
|
80561
|
+
db.delete(rawEventSamples).where(and58(
|
|
80435
80562
|
sourceScope,
|
|
80436
80563
|
sql26`${rawEventSamples.ts} GLOB ${CANONICAL_UTC_TIMESTAMP_GLOB}`,
|
|
80437
80564
|
lt11(rawEventSamples.ts, cutoff)
|
|
80438
80565
|
)).run();
|
|
80439
|
-
db.delete(rawEventSamples).where(
|
|
80566
|
+
db.delete(rawEventSamples).where(and58(
|
|
80440
80567
|
sourceScope,
|
|
80441
80568
|
sql26`${rawEventSamples.ts} NOT GLOB ${CANONICAL_UTC_TIMESTAMP_GLOB}`,
|
|
80442
80569
|
sql26`julianday(${rawEventSamples.ts}) < julianday(${cutoff})`
|
|
@@ -80485,16 +80612,16 @@ function writeTrafficEventBatch(opts) {
|
|
|
80485
80612
|
let aiReferralHits = 0;
|
|
80486
80613
|
let unknownHits = 0;
|
|
80487
80614
|
opts.db.transaction((tx) => {
|
|
80488
|
-
tx.update(trafficSources).set({ updatedAt: sql27`${trafficSources.updatedAt}` }).where(
|
|
80489
|
-
const source = tx.select().from(trafficSources).where(
|
|
80615
|
+
tx.update(trafficSources).set({ updatedAt: sql27`${trafficSources.updatedAt}` }).where(eq68(trafficSources.id, opts.sourceId)).run();
|
|
80616
|
+
const source = tx.select().from(trafficSources).where(eq68(trafficSources.id, opts.sourceId)).get();
|
|
80490
80617
|
opts.validateSource(source);
|
|
80491
80618
|
const rawSampleCutoff = enforceRawEventSampleRetention(
|
|
80492
80619
|
tx,
|
|
80493
80620
|
opts.sourceId,
|
|
80494
80621
|
opts.receivedAt
|
|
80495
80622
|
);
|
|
80496
|
-
tx.delete(trafficEventReceipts).where(
|
|
80497
|
-
|
|
80623
|
+
tx.delete(trafficEventReceipts).where(and59(
|
|
80624
|
+
eq68(trafficEventReceipts.sourceId, opts.sourceId),
|
|
80498
80625
|
lte15(trafficEventReceipts.expiresAt, opts.receivedAt)
|
|
80499
80626
|
)).run();
|
|
80500
80627
|
const claimedEvents = [];
|
|
@@ -80693,8 +80820,8 @@ function writeTrafficEventBatch(opts) {
|
|
|
80693
80820
|
if (!hour || hourlySampleLimit === 0) continue;
|
|
80694
80821
|
let remaining = remainingSamplesByHour.get(hour.start);
|
|
80695
80822
|
if (remaining === void 0) {
|
|
80696
|
-
const existing = tx.select({ total: sql27`COUNT(*)` }).from(rawEventSamples).where(
|
|
80697
|
-
|
|
80823
|
+
const existing = tx.select({ total: sql27`COUNT(*)` }).from(rawEventSamples).where(and59(
|
|
80824
|
+
eq68(rawEventSamples.sourceId, opts.sourceId),
|
|
80698
80825
|
gte15(rawEventSamples.ts, hour.start),
|
|
80699
80826
|
lt12(rawEventSamples.ts, hour.end)
|
|
80700
80827
|
)).get();
|
|
@@ -80735,7 +80862,7 @@ function writeTrafficEventBatch(opts) {
|
|
|
80735
80862
|
remainingSamplesByHour.set(hour.start, remaining - 1);
|
|
80736
80863
|
}
|
|
80737
80864
|
}
|
|
80738
|
-
tx.update(trafficSources).set(opts.sourceUpdate).where(
|
|
80865
|
+
tx.update(trafficSources).set(opts.sourceUpdate).where(eq68(trafficSources.id, opts.sourceId)).run();
|
|
80739
80866
|
});
|
|
80740
80867
|
return {
|
|
80741
80868
|
acceptedEvents,
|
|
@@ -80753,7 +80880,7 @@ function writeTrafficEventBatch(opts) {
|
|
|
80753
80880
|
}
|
|
80754
80881
|
|
|
80755
80882
|
// ../api-routes/src/traffic-sync-lease.ts
|
|
80756
|
-
import { and as
|
|
80883
|
+
import { and as and60, eq as eq69, isNull as isNull13, lte as lte16, or as or18 } from "drizzle-orm";
|
|
80757
80884
|
function leaseExpiresAt(now, ttlMs) {
|
|
80758
80885
|
const nowMs = Date.parse(now);
|
|
80759
80886
|
if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
|
|
@@ -80766,12 +80893,12 @@ function tryClaimTrafficSyncLease(input) {
|
|
|
80766
80893
|
const changed = tx.update(trafficSources).set({
|
|
80767
80894
|
syncLeaseOwner: input.owner,
|
|
80768
80895
|
syncLeaseExpiresAt: expiresAt
|
|
80769
|
-
}).where(
|
|
80770
|
-
|
|
80771
|
-
|
|
80896
|
+
}).where(and60(
|
|
80897
|
+
eq69(trafficSources.id, input.sourceId),
|
|
80898
|
+
or18(
|
|
80772
80899
|
isNull13(trafficSources.syncLeaseOwner),
|
|
80773
80900
|
lte16(trafficSources.syncLeaseExpiresAt, input.now),
|
|
80774
|
-
|
|
80901
|
+
eq69(trafficSources.syncLeaseOwner, input.owner)
|
|
80775
80902
|
)
|
|
80776
80903
|
)).run();
|
|
80777
80904
|
return changed.changes === 1;
|
|
@@ -80782,9 +80909,9 @@ function releaseTrafficSyncLease(input) {
|
|
|
80782
80909
|
const changed = tx.update(trafficSources).set({
|
|
80783
80910
|
syncLeaseOwner: null,
|
|
80784
80911
|
syncLeaseExpiresAt: null
|
|
80785
|
-
}).where(
|
|
80786
|
-
|
|
80787
|
-
|
|
80912
|
+
}).where(and60(
|
|
80913
|
+
eq69(trafficSources.id, input.sourceId),
|
|
80914
|
+
eq69(trafficSources.syncLeaseOwner, input.owner)
|
|
80788
80915
|
)).run();
|
|
80789
80916
|
return changed.changes === 1;
|
|
80790
80917
|
}, { behavior: "immediate" });
|
|
@@ -80865,7 +80992,7 @@ function cloudflareIngestRateLimitKey(request, store, db) {
|
|
|
80865
80992
|
const authenticated = authenticateCloudflareIngest(request, store);
|
|
80866
80993
|
const params = request.params;
|
|
80867
80994
|
if (authenticated && typeof params.name === "string" && authenticated.credential.projectName === params.name) {
|
|
80868
|
-
const source = db.select().from(trafficSources).where(
|
|
80995
|
+
const source = db.select().from(trafficSources).where(eq70(trafficSources.id, authenticated.credential.sourceId)).get();
|
|
80869
80996
|
if (source && source.sourceType === TrafficSourceTypes.cloudflare && source.status === TrafficSourceStatuses.connected && parseDirectPushCloudflareSourceConfig(source.configJson) && timingSafeEqualHex(authenticated.bearerHash, source.ingestTokenHash)) {
|
|
80870
80997
|
return `cloudflare-source:${authenticated.credential.sourceId}`;
|
|
80871
80998
|
}
|
|
@@ -81108,7 +81235,7 @@ async function defaultResolveAccessToken(record) {
|
|
|
81108
81235
|
);
|
|
81109
81236
|
}
|
|
81110
81237
|
function hasConnectedTrafficSourceSibling(db, projectId, sourceId) {
|
|
81111
|
-
return db.select().from(trafficSources).where(
|
|
81238
|
+
return db.select().from(trafficSources).where(eq70(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
|
|
81112
81239
|
}
|
|
81113
81240
|
function isAuthoritativeTrafficSource(db, source) {
|
|
81114
81241
|
return (source.status === TrafficSourceStatuses.connected || source.status === TrafficSourceStatuses.error) && !hasConnectedTrafficSourceSibling(db, source.projectId, source.id);
|
|
@@ -81132,13 +81259,13 @@ function wordpressBaseUrlIdentity(value) {
|
|
|
81132
81259
|
}
|
|
81133
81260
|
}
|
|
81134
81261
|
function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing = true) {
|
|
81135
|
-
const schedule = tx.select().from(schedules).where(
|
|
81136
|
-
|
|
81137
|
-
|
|
81262
|
+
const schedule = tx.select().from(schedules).where(and61(
|
|
81263
|
+
eq70(schedules.projectId, projectId),
|
|
81264
|
+
eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
81138
81265
|
)).get();
|
|
81139
81266
|
if (schedule) {
|
|
81140
81267
|
if (schedule.sourceId === sourceId) return { changed: false, created: false };
|
|
81141
|
-
tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(
|
|
81268
|
+
tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq70(schedules.id, schedule.id)).run();
|
|
81142
81269
|
return { changed: true, created: false };
|
|
81143
81270
|
}
|
|
81144
81271
|
if (!createIfMissing) return { changed: false, created: false };
|
|
@@ -81189,10 +81316,10 @@ async function runBackfillTask(options) {
|
|
|
81189
81316
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
81190
81317
|
try {
|
|
81191
81318
|
app.db.transaction((tx) => {
|
|
81192
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
81193
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
81319
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
|
|
81320
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
81194
81321
|
if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
|
|
81195
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
81322
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
81196
81323
|
}
|
|
81197
81324
|
});
|
|
81198
81325
|
} catch {
|
|
@@ -81219,7 +81346,7 @@ async function runBackfillTask(options) {
|
|
|
81219
81346
|
let emptyOutcome;
|
|
81220
81347
|
try {
|
|
81221
81348
|
const completed = app.db.transaction((tx) => {
|
|
81222
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
81349
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
81223
81350
|
const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2);
|
|
81224
81351
|
if (stillAuthoritative) {
|
|
81225
81352
|
enforceRawEventSampleRetention(tx, sourceRow2.id, finishedAt2);
|
|
@@ -81228,7 +81355,7 @@ async function runBackfillTask(options) {
|
|
|
81228
81355
|
status: RunStatuses.failed,
|
|
81229
81356
|
error: "Traffic source was deactivated or reconfigured during backfill",
|
|
81230
81357
|
finishedAt: finishedAt2
|
|
81231
|
-
}).where(
|
|
81358
|
+
}).where(eq70(runs.id, runId)).run();
|
|
81232
81359
|
return Boolean(stillAuthoritative);
|
|
81233
81360
|
});
|
|
81234
81361
|
emptyOutcome = completed ? { status: OutcomeStatuses.skipped, reasonCode: OutcomeReasonCodes.NO_DATA } : { status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_CONNECTED };
|
|
@@ -81254,13 +81381,13 @@ async function runBackfillTask(options) {
|
|
|
81254
81381
|
const nextLastSyncedAt = Math.max(currentLastSyncedMs, windowEnd.getTime()) === windowEnd.getTime() ? windowEndIso : sourceRow2.lastSyncedAt;
|
|
81255
81382
|
try {
|
|
81256
81383
|
const commitOutcome = app.db.transaction((tx) => {
|
|
81257
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
81384
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
81258
81385
|
if (latestSource?.sourceType === TrafficSourceTypes.wordpress) {
|
|
81259
81386
|
tx.update(runs).set({
|
|
81260
81387
|
status: RunStatuses.failed,
|
|
81261
81388
|
error: "Generic WordPress replace backfill is unavailable because retained coverage is unproven.",
|
|
81262
81389
|
finishedAt
|
|
81263
|
-
}).where(
|
|
81390
|
+
}).where(eq70(runs.id, runId)).run();
|
|
81264
81391
|
return "wordpress-backfill-unsupported";
|
|
81265
81392
|
}
|
|
81266
81393
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
|
|
@@ -81268,7 +81395,7 @@ async function runBackfillTask(options) {
|
|
|
81268
81395
|
status: RunStatuses.failed,
|
|
81269
81396
|
error: "Traffic source was deactivated or reconfigured during backfill",
|
|
81270
81397
|
finishedAt
|
|
81271
|
-
}).where(
|
|
81398
|
+
}).where(eq70(runs.id, runId)).run();
|
|
81272
81399
|
return "source-inactive";
|
|
81273
81400
|
}
|
|
81274
81401
|
const rawSampleCutoff = enforceRawEventSampleRetention(
|
|
@@ -81277,29 +81404,29 @@ async function runBackfillTask(options) {
|
|
|
81277
81404
|
finishedAt
|
|
81278
81405
|
);
|
|
81279
81406
|
tx.delete(crawlerEventsHourly).where(
|
|
81280
|
-
|
|
81281
|
-
|
|
81407
|
+
and61(
|
|
81408
|
+
eq70(crawlerEventsHourly.sourceId, sourceRow2.id),
|
|
81282
81409
|
gte16(crawlerEventsHourly.tsHour, windowStartIso),
|
|
81283
81410
|
lte17(crawlerEventsHourly.tsHour, windowEndIso)
|
|
81284
81411
|
)
|
|
81285
81412
|
).run();
|
|
81286
81413
|
tx.delete(aiUserFetchEventsHourly).where(
|
|
81287
|
-
|
|
81288
|
-
|
|
81414
|
+
and61(
|
|
81415
|
+
eq70(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
|
|
81289
81416
|
gte16(aiUserFetchEventsHourly.tsHour, windowStartIso),
|
|
81290
81417
|
lte17(aiUserFetchEventsHourly.tsHour, windowEndIso)
|
|
81291
81418
|
)
|
|
81292
81419
|
).run();
|
|
81293
81420
|
tx.delete(aiReferralEventsHourly).where(
|
|
81294
|
-
|
|
81295
|
-
|
|
81421
|
+
and61(
|
|
81422
|
+
eq70(aiReferralEventsHourly.sourceId, sourceRow2.id),
|
|
81296
81423
|
gte16(aiReferralEventsHourly.tsHour, windowStartIso),
|
|
81297
81424
|
lte17(aiReferralEventsHourly.tsHour, windowEndIso)
|
|
81298
81425
|
)
|
|
81299
81426
|
).run();
|
|
81300
81427
|
tx.delete(rawEventSamples).where(
|
|
81301
|
-
|
|
81302
|
-
|
|
81428
|
+
and61(
|
|
81429
|
+
eq70(rawEventSamples.sourceId, sourceRow2.id),
|
|
81303
81430
|
gte16(rawEventSamples.ts, windowStartIso),
|
|
81304
81431
|
lte17(rawEventSamples.ts, windowEndIso)
|
|
81305
81432
|
)
|
|
@@ -81491,8 +81618,8 @@ async function runBackfillTask(options) {
|
|
|
81491
81618
|
lastEventIds: newRingBuffer,
|
|
81492
81619
|
...skipRecovered ? { skippedThroughAt: null } : {},
|
|
81493
81620
|
updatedAt: finishedAt
|
|
81494
|
-
}).where(
|
|
81495
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
81621
|
+
}).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
81622
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq70(runs.id, runId)).run();
|
|
81496
81623
|
return "committed";
|
|
81497
81624
|
});
|
|
81498
81625
|
reportBackfill(commitOutcome === "committed" ? {
|
|
@@ -81654,7 +81781,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81654
81781
|
};
|
|
81655
81782
|
const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
|
|
81656
81783
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
81657
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
81784
|
+
const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
|
|
81658
81785
|
const sourceId = activeSource?.id ?? crypto48.randomUUID();
|
|
81659
81786
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
81660
81787
|
if (activeSource) {
|
|
@@ -81664,7 +81791,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81664
81791
|
lastError: null,
|
|
81665
81792
|
configJson: config,
|
|
81666
81793
|
updatedAt: now
|
|
81667
|
-
}).where(
|
|
81794
|
+
}).where(eq70(trafficSources.id, sourceId)).run();
|
|
81668
81795
|
} else {
|
|
81669
81796
|
tx.insert(trafficSources).values({
|
|
81670
81797
|
id: sourceId,
|
|
@@ -81690,7 +81817,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81690
81817
|
entityId: sourceId
|
|
81691
81818
|
});
|
|
81692
81819
|
return {
|
|
81693
|
-
sourceRow: tx.select().from(trafficSources).where(
|
|
81820
|
+
sourceRow: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get(),
|
|
81694
81821
|
scheduleChanged: scheduleBinding.changed
|
|
81695
81822
|
};
|
|
81696
81823
|
}, { behavior: "immediate" });
|
|
@@ -81745,7 +81872,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81745
81872
|
const config = { baseUrl, username };
|
|
81746
81873
|
const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
|
|
81747
81874
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
81748
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
81875
|
+
const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
|
|
81749
81876
|
const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
|
|
81750
81877
|
const sourceId = endpointChanged ? crypto48.randomUUID() : activeSource?.id ?? crypto48.randomUUID();
|
|
81751
81878
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
@@ -81756,14 +81883,14 @@ async function trafficRoutes(app, opts) {
|
|
|
81756
81883
|
lastError: null,
|
|
81757
81884
|
configJson: config,
|
|
81758
81885
|
updatedAt: now
|
|
81759
|
-
}).where(
|
|
81886
|
+
}).where(eq70(trafficSources.id, sourceId)).run();
|
|
81760
81887
|
} else {
|
|
81761
81888
|
if (activeSource) {
|
|
81762
81889
|
tx.update(trafficSources).set({
|
|
81763
81890
|
status: TrafficSourceStatuses.archived,
|
|
81764
81891
|
archivedAt: now,
|
|
81765
81892
|
updatedAt: now
|
|
81766
|
-
}).where(
|
|
81893
|
+
}).where(eq70(trafficSources.id, activeSource.id)).run();
|
|
81767
81894
|
}
|
|
81768
81895
|
tx.insert(trafficSources).values({
|
|
81769
81896
|
id: sourceId,
|
|
@@ -81789,7 +81916,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81789
81916
|
entityId: sourceId
|
|
81790
81917
|
});
|
|
81791
81918
|
return {
|
|
81792
|
-
sourceRow: tx.select().from(trafficSources).where(
|
|
81919
|
+
sourceRow: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get(),
|
|
81793
81920
|
scheduleChanged: scheduleBinding.changed
|
|
81794
81921
|
};
|
|
81795
81922
|
}, { behavior: "immediate" });
|
|
@@ -81846,7 +81973,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81846
81973
|
const config = { projectId, teamId, environment };
|
|
81847
81974
|
const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
|
|
81848
81975
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
81849
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
81976
|
+
const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
|
|
81850
81977
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
81851
81978
|
let row;
|
|
81852
81979
|
if (activeSource) {
|
|
@@ -81856,8 +81983,8 @@ async function trafficRoutes(app, opts) {
|
|
|
81856
81983
|
lastError: null,
|
|
81857
81984
|
configJson: config,
|
|
81858
81985
|
updatedAt: now
|
|
81859
|
-
}).where(
|
|
81860
|
-
row = tx.select().from(trafficSources).where(
|
|
81986
|
+
}).where(eq70(trafficSources.id, activeSource.id)).run();
|
|
81987
|
+
row = tx.select().from(trafficSources).where(eq70(trafficSources.id, activeSource.id)).get();
|
|
81861
81988
|
} else {
|
|
81862
81989
|
const newId = crypto48.randomUUID();
|
|
81863
81990
|
tx.insert(trafficSources).values({
|
|
@@ -81882,7 +82009,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81882
82009
|
createdAt: now,
|
|
81883
82010
|
updatedAt: now
|
|
81884
82011
|
}).run();
|
|
81885
|
-
row = tx.select().from(trafficSources).where(
|
|
82012
|
+
row = tx.select().from(trafficSources).where(eq70(trafficSources.id, newId)).get();
|
|
81886
82013
|
}
|
|
81887
82014
|
const scheduleBinding = sourceStatus === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, row.id, now) : { changed: false, created: false };
|
|
81888
82015
|
writeAuditLog(tx, {
|
|
@@ -81926,7 +82053,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81926
82053
|
const { displayName: displayName2, zoneId: zoneId2, accountId: accountId2, queueId, queueName, retentionSeconds, apiToken } = parsed.data;
|
|
81927
82054
|
const workerRouteHost2 = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
|
|
81928
82055
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
81929
|
-
const cloudflareSources = app.db.select().from(trafficSources).where(
|
|
82056
|
+
const cloudflareSources = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
|
|
81930
82057
|
const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
|
|
81931
82058
|
if (sameModeSource) attempt.update({ action: "reauth" });
|
|
81932
82059
|
const sourceId2 = sameModeSource?.id ?? crypto48.randomUUID();
|
|
@@ -81977,7 +82104,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81977
82104
|
let scheduleChanged = false;
|
|
81978
82105
|
try {
|
|
81979
82106
|
const result = app.db.transaction((tx) => {
|
|
81980
|
-
const currentSameModeSource = tx.select().from(trafficSources).where(
|
|
82107
|
+
const currentSameModeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseQueuePullCloudflareSourceConfig(row.configJson) !== null);
|
|
81981
82108
|
if ((currentSameModeSource?.id ?? null) !== (sameModeSource?.id ?? null)) {
|
|
81982
82109
|
throw operationInProgress("Cloudflare Queue source changed during connect; retry");
|
|
81983
82110
|
}
|
|
@@ -81995,7 +82122,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81995
82122
|
queueBacklogObservedAt: null
|
|
81996
82123
|
} : {},
|
|
81997
82124
|
updatedAt: now2
|
|
81998
|
-
}).where(
|
|
82125
|
+
}).where(eq70(trafficSources.id, sourceId2)).run();
|
|
81999
82126
|
} else {
|
|
82000
82127
|
tx.insert(trafficSources).values({
|
|
82001
82128
|
id: sourceId2,
|
|
@@ -82015,7 +82142,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82015
82142
|
}
|
|
82016
82143
|
const changed = status === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, sourceId2, now2).changed : false;
|
|
82017
82144
|
return {
|
|
82018
|
-
row: tx.select().from(trafficSources).where(
|
|
82145
|
+
row: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId2)).get(),
|
|
82019
82146
|
scheduleChanged: changed
|
|
82020
82147
|
};
|
|
82021
82148
|
}, { behavior: "immediate" });
|
|
@@ -82063,7 +82190,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82063
82190
|
const { deliveryMode, displayName, zoneId, accountId } = parsed.data;
|
|
82064
82191
|
const workerRouteHost = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
|
|
82065
82192
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
82066
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
82193
|
+
const activeSource = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
|
|
82067
82194
|
if (activeSource) attempt.update({ action: "reauth" });
|
|
82068
82195
|
const sourceId = activeSource?.id ?? crypto48.randomUUID();
|
|
82069
82196
|
const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
|
|
@@ -82128,7 +82255,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82128
82255
|
let scheduleRemoved = false;
|
|
82129
82256
|
try {
|
|
82130
82257
|
const result = app.db.transaction((tx) => {
|
|
82131
|
-
const currentActiveSource = tx.select().from(trafficSources).where(
|
|
82258
|
+
const currentActiveSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.cloudflare && row2.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row2.configJson) !== null);
|
|
82132
82259
|
if ((currentActiveSource?.id ?? null) !== (activeSource?.id ?? null)) {
|
|
82133
82260
|
throw operationInProgress("Cloudflare direct-push source changed during connect; retry");
|
|
82134
82261
|
}
|
|
@@ -82142,8 +82269,8 @@ async function trafficRoutes(app, opts) {
|
|
|
82142
82269
|
configJson: config,
|
|
82143
82270
|
ingestTokenHash,
|
|
82144
82271
|
updatedAt: now
|
|
82145
|
-
}).where(
|
|
82146
|
-
row = tx.select().from(trafficSources).where(
|
|
82272
|
+
}).where(eq70(trafficSources.id, currentActiveSource.id)).run();
|
|
82273
|
+
row = tx.select().from(trafficSources).where(eq70(trafficSources.id, currentActiveSource.id)).get();
|
|
82147
82274
|
} else {
|
|
82148
82275
|
tx.insert(trafficSources).values({
|
|
82149
82276
|
id: sourceId,
|
|
@@ -82163,16 +82290,16 @@ async function trafficRoutes(app, opts) {
|
|
|
82163
82290
|
createdAt: now,
|
|
82164
82291
|
updatedAt: now
|
|
82165
82292
|
}).run();
|
|
82166
|
-
row = tx.select().from(trafficSources).where(
|
|
82293
|
+
row = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get();
|
|
82167
82294
|
}
|
|
82168
82295
|
let removed = false;
|
|
82169
82296
|
if (row.status === TrafficSourceStatuses.connected) {
|
|
82170
|
-
const schedule = tx.select().from(schedules).where(
|
|
82171
|
-
|
|
82172
|
-
|
|
82297
|
+
const schedule = tx.select().from(schedules).where(and61(
|
|
82298
|
+
eq70(schedules.projectId, project.id),
|
|
82299
|
+
eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
82173
82300
|
)).get();
|
|
82174
82301
|
if (schedule) {
|
|
82175
|
-
tx.delete(schedules).where(
|
|
82302
|
+
tx.delete(schedules).where(eq70(schedules.id, schedule.id)).run();
|
|
82176
82303
|
removed = true;
|
|
82177
82304
|
}
|
|
82178
82305
|
}
|
|
@@ -82226,47 +82353,47 @@ async function trafficRoutes(app, opts) {
|
|
|
82226
82353
|
};
|
|
82227
82354
|
}));
|
|
82228
82355
|
app.post("/projects/:name/traffic/sources/:id/activate", connectionRoute(app, (request) => {
|
|
82229
|
-
const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(
|
|
82356
|
+
const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
|
|
82230
82357
|
const integration = source ? TRAFFIC_SOURCE_INTEGRATIONS[source.sourceType] : void 0;
|
|
82231
82358
|
return integration ? { integration, action: "select" } : void 0;
|
|
82232
82359
|
}, async (request) => {
|
|
82233
82360
|
const project = resolveProject(app.db, request.params.name);
|
|
82234
82361
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
82235
82362
|
const result = app.db.transaction((tx) => {
|
|
82236
|
-
const target = tx.select().from(trafficSources).where(
|
|
82363
|
+
const target = tx.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
|
|
82237
82364
|
if (!target || target.projectId !== project.id || target.status === TrafficSourceStatuses.archived) {
|
|
82238
82365
|
throw notFound("Traffic source", request.params.id);
|
|
82239
82366
|
}
|
|
82240
82367
|
const { usesPullSchedule } = validateTrafficSourceCredential(target, project.name);
|
|
82241
|
-
const siblings = tx.select().from(trafficSources).where(
|
|
82368
|
+
const siblings = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all();
|
|
82242
82369
|
for (const sibling of siblings) {
|
|
82243
82370
|
if (sibling.id === target.id || sibling.status === TrafficSourceStatuses.archived) continue;
|
|
82244
82371
|
tx.update(trafficSources).set({
|
|
82245
82372
|
status: TrafficSourceStatuses.paused,
|
|
82246
82373
|
updatedAt: now
|
|
82247
|
-
}).where(
|
|
82374
|
+
}).where(eq70(trafficSources.id, sibling.id)).run();
|
|
82248
82375
|
}
|
|
82249
82376
|
tx.update(trafficSources).set({
|
|
82250
82377
|
status: TrafficSourceStatuses.connected,
|
|
82251
82378
|
lastError: null,
|
|
82252
82379
|
archivedAt: null,
|
|
82253
82380
|
updatedAt: now
|
|
82254
|
-
}).where(
|
|
82381
|
+
}).where(eq70(trafficSources.id, target.id)).run();
|
|
82255
82382
|
let scheduleAction = null;
|
|
82256
82383
|
if (usesPullSchedule) {
|
|
82257
82384
|
bindTrafficSyncSchedule(tx, project.id, target.id, now);
|
|
82258
82385
|
scheduleAction = "upsert";
|
|
82259
82386
|
} else {
|
|
82260
|
-
const schedule = tx.select().from(schedules).where(
|
|
82261
|
-
|
|
82262
|
-
|
|
82387
|
+
const schedule = tx.select().from(schedules).where(and61(
|
|
82388
|
+
eq70(schedules.projectId, project.id),
|
|
82389
|
+
eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
82263
82390
|
)).get();
|
|
82264
82391
|
if (schedule) {
|
|
82265
|
-
tx.delete(schedules).where(
|
|
82392
|
+
tx.delete(schedules).where(eq70(schedules.id, schedule.id)).run();
|
|
82266
82393
|
scheduleAction = "delete";
|
|
82267
82394
|
}
|
|
82268
82395
|
}
|
|
82269
|
-
const source = tx.select().from(trafficSources).where(
|
|
82396
|
+
const source = tx.select().from(trafficSources).where(eq70(trafficSources.id, target.id)).get();
|
|
82270
82397
|
writeAuditLog(tx, {
|
|
82271
82398
|
projectId: project.id,
|
|
82272
82399
|
actor: "api",
|
|
@@ -82334,7 +82461,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82334
82461
|
}
|
|
82335
82462
|
const { bearerHash, credential } = authenticated;
|
|
82336
82463
|
const sourceId = credential.sourceId;
|
|
82337
|
-
const sourceRow2 = app.db.select().from(trafficSources).where(
|
|
82464
|
+
const sourceRow2 = app.db.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get();
|
|
82338
82465
|
if (!sourceRow2 || sourceRow2.sourceType !== TrafficSourceTypes.cloudflare || sourceRow2.status !== TrafficSourceStatuses.connected || !parseDirectPushCloudflareSourceConfig(sourceRow2.configJson) || !timingSafeEqualHex(bearerHash, sourceRow2.ingestTokenHash)) {
|
|
82339
82466
|
throw authRequired();
|
|
82340
82467
|
}
|
|
@@ -82408,7 +82535,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82408
82535
|
});
|
|
82409
82536
|
app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
|
|
82410
82537
|
const project = resolveProject(app.db, request.params.name);
|
|
82411
|
-
const initialSourceRow = app.db.select().from(trafficSources).where(
|
|
82538
|
+
const initialSourceRow = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
|
|
82412
82539
|
if (!initialSourceRow || initialSourceRow.projectId !== project.id) {
|
|
82413
82540
|
throw notFound("Traffic source", request.params.id);
|
|
82414
82541
|
}
|
|
@@ -82563,7 +82690,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82563
82690
|
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
82564
82691
|
ttlMs: CLOUDFLARE_QUEUE_SYNC_LEASE_TTL_MS
|
|
82565
82692
|
})) throw new Error("Queue sync lease was lost before acknowledgement");
|
|
82566
|
-
const latestSource = app.db.select().from(trafficSources).where(
|
|
82693
|
+
const latestSource = app.db.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
82567
82694
|
if (!latestSource || latestSource.projectId !== project.id || latestSource.status !== TrafficSourceStatuses.connected || latestSource.syncLeaseOwner !== leaseOwner || !isDeepStrictEqual2(latestSource.configJson, sourceRow2.configJson)) {
|
|
82568
82695
|
throw validationError("Cloudflare Queue source was reconfigured before acknowledgement");
|
|
82569
82696
|
}
|
|
@@ -82588,7 +82715,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82588
82715
|
const pulledEnvelopeCount = pulled.messages.length + pulled.skippedUnleasedMessageCount;
|
|
82589
82716
|
if (pulledEnvelopeCount < CLOUDFLARE_QUEUE_BATCH_SIZE || remainingBacklogCount === 0) break;
|
|
82590
82717
|
}
|
|
82591
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: committedAt }).where(
|
|
82718
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: committedAt }).where(eq70(runs.id, runId)).run();
|
|
82592
82719
|
writeAuditLog(app.db, {
|
|
82593
82720
|
projectId: project.id,
|
|
82594
82721
|
actor: "api",
|
|
@@ -82649,13 +82776,13 @@ async function trafficRoutes(app, opts) {
|
|
|
82649
82776
|
} catch {
|
|
82650
82777
|
}
|
|
82651
82778
|
app.db.transaction((tx) => {
|
|
82652
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: safeError, finishedAt: failedAt }).where(
|
|
82653
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
82779
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: safeError, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
|
|
82780
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
82654
82781
|
if (latestSource && latestSource.status === TrafficSourceStatuses.connected && latestSource.syncLeaseOwner === leaseOwner && isDeepStrictEqual2(latestSource.configJson, sourceRow2.configJson)) {
|
|
82655
|
-
tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(
|
|
82656
|
-
|
|
82657
|
-
|
|
82658
|
-
|
|
82782
|
+
tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and61(
|
|
82783
|
+
eq70(trafficSources.id, sourceRow2.id),
|
|
82784
|
+
eq70(trafficSources.status, TrafficSourceStatuses.connected),
|
|
82785
|
+
eq70(trafficSources.syncLeaseOwner, leaseOwner)
|
|
82659
82786
|
)).run();
|
|
82660
82787
|
}
|
|
82661
82788
|
});
|
|
@@ -82674,7 +82801,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82674
82801
|
`Sync for source type "${sourceRow2.sourceType}" is not implemented yet \u2014 only cloud-run, wordpress, and vercel are supported in v1.`
|
|
82675
82802
|
);
|
|
82676
82803
|
}
|
|
82677
|
-
const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(
|
|
82804
|
+
const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
|
|
82678
82805
|
if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
|
|
82679
82806
|
throw validationError("Traffic source must be active before it can sync");
|
|
82680
82807
|
}
|
|
@@ -82707,10 +82834,10 @@ async function trafficRoutes(app, opts) {
|
|
|
82707
82834
|
const markFailed = (msg, errorCode2) => {
|
|
82708
82835
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
82709
82836
|
app.db.transaction((tx) => {
|
|
82710
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
82711
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
82837
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
|
|
82838
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
82712
82839
|
if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && (wordpressLeaseOwner === void 0 || latestSource.syncLeaseOwner === wordpressLeaseOwner)) {
|
|
82713
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
82840
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
82714
82841
|
}
|
|
82715
82842
|
});
|
|
82716
82843
|
try {
|
|
@@ -82804,7 +82931,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82804
82931
|
}
|
|
82805
82932
|
const credential = credentialStore.getConnection(project.name);
|
|
82806
82933
|
if (!credential) {
|
|
82807
|
-
app.db.delete(runs).where(
|
|
82934
|
+
app.db.delete(runs).where(eq70(runs.id, runId)).run();
|
|
82808
82935
|
throw validationError(
|
|
82809
82936
|
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
82810
82937
|
);
|
|
@@ -82838,14 +82965,14 @@ async function trafficRoutes(app, opts) {
|
|
|
82838
82965
|
const windowEndIso2 = windowEnd.toISOString();
|
|
82839
82966
|
const reservedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
82840
82967
|
const reservedSource = app.db.transaction((tx) => {
|
|
82841
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
82968
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
82842
82969
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== wordpressLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
|
|
82843
82970
|
tx.update(trafficSources).set({
|
|
82844
82971
|
lastSyncedAt: windowStartIso2,
|
|
82845
82972
|
wordpressPendingUntil: windowEndIso2,
|
|
82846
82973
|
updatedAt: reservedAt
|
|
82847
|
-
}).where(
|
|
82848
|
-
return tx.select().from(trafficSources).where(
|
|
82974
|
+
}).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
82975
|
+
return tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
82849
82976
|
}, { behavior: "immediate" });
|
|
82850
82977
|
if (!reservedSource) {
|
|
82851
82978
|
const message = "Traffic source changed while reserving the WordPress sync window; retry the sync.";
|
|
@@ -82905,12 +83032,12 @@ async function trafficRoutes(app, opts) {
|
|
|
82905
83032
|
auditAction = "traffic.vercel.synced";
|
|
82906
83033
|
const credentialStore = opts.vercelTrafficCredentialStore;
|
|
82907
83034
|
if (!credentialStore) {
|
|
82908
|
-
app.db.delete(runs).where(
|
|
83035
|
+
app.db.delete(runs).where(eq70(runs.id, runId)).run();
|
|
82909
83036
|
throw validationError("Vercel traffic credential storage is not configured for this deployment");
|
|
82910
83037
|
}
|
|
82911
83038
|
const credential = credentialStore.getConnection(project.name);
|
|
82912
83039
|
if (!credential) {
|
|
82913
|
-
app.db.delete(runs).where(
|
|
83040
|
+
app.db.delete(runs).where(eq70(runs.id, runId)).run();
|
|
82914
83041
|
throw validationError(
|
|
82915
83042
|
`No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
|
|
82916
83043
|
);
|
|
@@ -82928,7 +83055,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82928
83055
|
if (cappedStartMs > clampedStartMs) {
|
|
82929
83056
|
const previousSkip = sourceRow2.skippedThroughAt ? Date.parse(sourceRow2.skippedThroughAt) : Number.NaN;
|
|
82930
83057
|
const skippedThrough = Number.isFinite(previousSkip) ? new Date(Math.max(previousSkip, cappedStartMs)) : new Date(cappedStartMs);
|
|
82931
|
-
app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(
|
|
83058
|
+
app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
82932
83059
|
request.log.warn(
|
|
82933
83060
|
{
|
|
82934
83061
|
sourceId: sourceRow2.id,
|
|
@@ -83021,15 +83148,15 @@ async function trafficRoutes(app, opts) {
|
|
|
83021
83148
|
throw providerError(`WordPress sync failed: ${msg}`);
|
|
83022
83149
|
}
|
|
83023
83150
|
const commitOutcome = app.db.transaction((tx) => {
|
|
83024
|
-
const latestRow = tx.select().from(trafficSources).where(
|
|
83025
|
-
const latestHasConnectedSibling = tx.select().from(trafficSources).where(
|
|
83151
|
+
const latestRow = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
83152
|
+
const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
|
|
83026
83153
|
if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || wordpressLeaseOwner !== void 0 && latestRow.syncLeaseOwner !== wordpressLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
|
|
83027
83154
|
const abortedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
83028
83155
|
tx.update(runs).set({
|
|
83029
83156
|
status: RunStatuses.failed,
|
|
83030
83157
|
error: "Traffic source was deactivated or reconfigured during sync",
|
|
83031
83158
|
finishedAt: abortedAt
|
|
83032
|
-
}).where(
|
|
83159
|
+
}).where(eq70(runs.id, runId)).run();
|
|
83033
83160
|
return "source-inactive";
|
|
83034
83161
|
}
|
|
83035
83162
|
const previousIds = latestRow.lastEventIds ?? [];
|
|
@@ -83292,8 +83419,8 @@ async function trafficRoutes(app, opts) {
|
|
|
83292
83419
|
sourceUpdate.lastCursor = nextCursor ?? null;
|
|
83293
83420
|
sourceUpdate.wordpressPendingUntil = nextCursor ? windowEnd.toISOString() : null;
|
|
83294
83421
|
}
|
|
83295
|
-
tx.update(trafficSources).set(sourceUpdate).where(
|
|
83296
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
83422
|
+
tx.update(trafficSources).set(sourceUpdate).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
83423
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq70(runs.id, runId)).run();
|
|
83297
83424
|
return "committed";
|
|
83298
83425
|
}, { behavior: "immediate" });
|
|
83299
83426
|
if (commitOutcome === "source-inactive") {
|
|
@@ -83363,7 +83490,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83363
83490
|
});
|
|
83364
83491
|
app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => withFeatureOutcome(app, { feature: "server_traffic", operation: "backfill" }, async (recordOutcome) => {
|
|
83365
83492
|
const project = resolveProject(app.db, request.params.name);
|
|
83366
|
-
const sourceRow2 = app.db.select().from(trafficSources).where(
|
|
83493
|
+
const sourceRow2 = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
|
|
83367
83494
|
if (!sourceRow2 || sourceRow2.projectId !== project.id) {
|
|
83368
83495
|
throw notFound("Traffic source", request.params.id);
|
|
83369
83496
|
}
|
|
@@ -83476,7 +83603,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83476
83603
|
const startedAt = windowEnd.toISOString();
|
|
83477
83604
|
const runId = crypto48.randomUUID();
|
|
83478
83605
|
app.db.transaction((tx) => {
|
|
83479
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
83606
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
83480
83607
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
|
|
83481
83608
|
throw validationError("Traffic source must remain active and unchanged before it can backfill");
|
|
83482
83609
|
}
|
|
@@ -83519,8 +83646,8 @@ async function trafficRoutes(app, opts) {
|
|
|
83519
83646
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
83520
83647
|
hits: sql28`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
83521
83648
|
}).from(crawlerEventsHourly).where(
|
|
83522
|
-
|
|
83523
|
-
|
|
83649
|
+
and61(
|
|
83650
|
+
eq70(crawlerEventsHourly.sourceId, row.id),
|
|
83524
83651
|
gte16(crawlerEventsHourly.tsHour, since)
|
|
83525
83652
|
)
|
|
83526
83653
|
).groupBy(crawlerEventsHourly.pathNormalized).all();
|
|
@@ -83529,8 +83656,8 @@ async function trafficRoutes(app, opts) {
|
|
|
83529
83656
|
);
|
|
83530
83657
|
const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
|
|
83531
83658
|
const aiUserFetchTotals = app.db.select({ total: sql28`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
83532
|
-
|
|
83533
|
-
|
|
83659
|
+
and61(
|
|
83660
|
+
eq70(aiUserFetchEventsHourly.sourceId, row.id),
|
|
83534
83661
|
gte16(aiUserFetchEventsHourly.tsHour, since)
|
|
83535
83662
|
)
|
|
83536
83663
|
).get();
|
|
@@ -83539,22 +83666,22 @@ async function trafficRoutes(app, opts) {
|
|
|
83539
83666
|
nonhop: sql28`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
|
|
83540
83667
|
landed: sql28`COALESCE(SUM(CASE WHEN ${countableReferralCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`
|
|
83541
83668
|
}).from(aiReferralEventsHourly).where(
|
|
83542
|
-
|
|
83543
|
-
|
|
83669
|
+
and61(
|
|
83670
|
+
eq70(aiReferralEventsHourly.sourceId, row.id),
|
|
83544
83671
|
gte16(aiReferralEventsHourly.tsHour, since)
|
|
83545
83672
|
)
|
|
83546
83673
|
).get();
|
|
83547
83674
|
const sampleTotals = app.db.select({ total: sql28`COUNT(*)` }).from(rawEventSamples).where(
|
|
83548
|
-
|
|
83549
|
-
|
|
83675
|
+
and61(
|
|
83676
|
+
eq70(rawEventSamples.sourceId, row.id),
|
|
83550
83677
|
gte16(rawEventSamples.ts, since)
|
|
83551
83678
|
)
|
|
83552
83679
|
).get();
|
|
83553
83680
|
const latestRun = app.db.select().from(runs).where(
|
|
83554
|
-
|
|
83555
|
-
|
|
83556
|
-
|
|
83557
|
-
|
|
83681
|
+
and61(
|
|
83682
|
+
eq70(runs.projectId, projectId),
|
|
83683
|
+
eq70(runs.kind, RunKinds["traffic-sync"]),
|
|
83684
|
+
eq70(runs.sourceId, row.id)
|
|
83558
83685
|
)
|
|
83559
83686
|
).orderBy(desc35(runs.startedAt)).limit(1).get();
|
|
83560
83687
|
return {
|
|
@@ -83590,7 +83717,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83590
83717
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83591
83718
|
let updatedRow;
|
|
83592
83719
|
app.db.transaction((tx) => {
|
|
83593
|
-
const sourceRow2 = tx.select().from(trafficSources).where(
|
|
83720
|
+
const sourceRow2 = tx.select().from(trafficSources).where(and61(eq70(trafficSources.projectId, project.id), eq70(trafficSources.id, request.params.id))).get();
|
|
83594
83721
|
if (!sourceRow2) {
|
|
83595
83722
|
throw notFound("traffic source", request.params.id);
|
|
83596
83723
|
}
|
|
@@ -83604,7 +83731,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83604
83731
|
`Traffic source "${sourceRow2.id}" is staged. Activate it explicitly instead of resetting it.`
|
|
83605
83732
|
);
|
|
83606
83733
|
}
|
|
83607
|
-
const hasConnectedSibling = tx.select().from(trafficSources).where(
|
|
83734
|
+
const hasConnectedSibling = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
|
|
83608
83735
|
if (hasConnectedSibling) {
|
|
83609
83736
|
throw validationError(
|
|
83610
83737
|
`Traffic source "${sourceRow2.id}" is not authoritative. Activate it explicitly instead of resetting it.`
|
|
@@ -83621,7 +83748,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83621
83748
|
status: TrafficSourceStatuses.connected,
|
|
83622
83749
|
lastError: null,
|
|
83623
83750
|
updatedAt: now
|
|
83624
|
-
}).where(
|
|
83751
|
+
}).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
83625
83752
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
83626
83753
|
projectId: project.id,
|
|
83627
83754
|
actor: "api",
|
|
@@ -83629,20 +83756,20 @@ async function trafficRoutes(app, opts) {
|
|
|
83629
83756
|
entityType: "traffic_source",
|
|
83630
83757
|
entityId: sourceRow2.id
|
|
83631
83758
|
}));
|
|
83632
|
-
updatedRow = tx.select().from(trafficSources).where(
|
|
83759
|
+
updatedRow = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
83633
83760
|
});
|
|
83634
83761
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
83635
83762
|
}));
|
|
83636
83763
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
83637
83764
|
const project = resolveProject(app.db, request.params.name);
|
|
83638
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
83765
|
+
const rows = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
|
|
83639
83766
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
|
|
83640
83767
|
const response = { sources };
|
|
83641
83768
|
return response;
|
|
83642
83769
|
});
|
|
83643
83770
|
app.get("/projects/:name/traffic/status", async (request) => {
|
|
83644
83771
|
const project = resolveProject(app.db, request.params.name);
|
|
83645
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
83772
|
+
const rows = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
|
|
83646
83773
|
const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
|
|
83647
83774
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
|
|
83648
83775
|
const response = { sources };
|
|
@@ -83652,7 +83779,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83652
83779
|
"/projects/:name/traffic/sources/:id",
|
|
83653
83780
|
async (request) => {
|
|
83654
83781
|
const project = resolveProject(app.db, request.params.name);
|
|
83655
|
-
const row = app.db.select().from(trafficSources).where(
|
|
83782
|
+
const row = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
|
|
83656
83783
|
if (!row || row.projectId !== project.id) {
|
|
83657
83784
|
throw notFound("Traffic source", request.params.id);
|
|
83658
83785
|
}
|
|
@@ -83725,12 +83852,12 @@ async function trafficRoutes(app, opts) {
|
|
|
83725
83852
|
const seriesByBucket = /* @__PURE__ */ new Map();
|
|
83726
83853
|
if (kind === "all" || kind === TrafficEventKinds.crawler) {
|
|
83727
83854
|
const crawlerFilters = [
|
|
83728
|
-
|
|
83855
|
+
eq70(crawlerEventsHourly.projectId, project.id),
|
|
83729
83856
|
gte16(crawlerEventsHourly.tsHour, sinceIso),
|
|
83730
83857
|
lte17(crawlerEventsHourly.tsHour, untilIso)
|
|
83731
83858
|
];
|
|
83732
|
-
if (sourceIdParam) crawlerFilters.push(
|
|
83733
|
-
const crawlerWhere =
|
|
83859
|
+
if (sourceIdParam) crawlerFilters.push(eq70(crawlerEventsHourly.sourceId, sourceIdParam));
|
|
83860
|
+
const crawlerWhere = and61(...crawlerFilters);
|
|
83734
83861
|
const pathTotals = app.db.select({
|
|
83735
83862
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
83736
83863
|
hits: sql28`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
|
|
@@ -83800,12 +83927,12 @@ async function trafficRoutes(app, opts) {
|
|
|
83800
83927
|
}
|
|
83801
83928
|
if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
|
|
83802
83929
|
const userFetchFilters = [
|
|
83803
|
-
|
|
83930
|
+
eq70(aiUserFetchEventsHourly.projectId, project.id),
|
|
83804
83931
|
gte16(aiUserFetchEventsHourly.tsHour, sinceIso),
|
|
83805
83932
|
lte17(aiUserFetchEventsHourly.tsHour, untilIso)
|
|
83806
83933
|
];
|
|
83807
|
-
if (sourceIdParam) userFetchFilters.push(
|
|
83808
|
-
const userFetchWhere =
|
|
83934
|
+
if (sourceIdParam) userFetchFilters.push(eq70(aiUserFetchEventsHourly.sourceId, sourceIdParam));
|
|
83935
|
+
const userFetchWhere = and61(...userFetchFilters);
|
|
83809
83936
|
const total = app.db.select({
|
|
83810
83937
|
total: sql28`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
|
|
83811
83938
|
rows: sql28`COUNT(*)`
|
|
@@ -83839,12 +83966,12 @@ async function trafficRoutes(app, opts) {
|
|
|
83839
83966
|
}
|
|
83840
83967
|
if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
|
|
83841
83968
|
const aiFilters = [
|
|
83842
|
-
|
|
83969
|
+
eq70(aiReferralEventsHourly.projectId, project.id),
|
|
83843
83970
|
gte16(aiReferralEventsHourly.tsHour, sinceIso),
|
|
83844
83971
|
lte17(aiReferralEventsHourly.tsHour, untilIso)
|
|
83845
83972
|
];
|
|
83846
|
-
if (sourceIdParam) aiFilters.push(
|
|
83847
|
-
const aiWhere =
|
|
83973
|
+
if (sourceIdParam) aiFilters.push(eq70(aiReferralEventsHourly.sourceId, sourceIdParam));
|
|
83974
|
+
const aiWhere = and61(...aiFilters);
|
|
83848
83975
|
const total = app.db.select({
|
|
83849
83976
|
total: sql28`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
|
|
83850
83977
|
nonhop: sql28`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
|
|
@@ -83919,7 +84046,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83919
84046
|
windowEnd: untilIso,
|
|
83920
84047
|
series: (() => {
|
|
83921
84048
|
const points = completeTrafficSeries(since, until, granularity, seriesByBucket);
|
|
83922
|
-
const coverageScope = (col, srcCol) => sourceIdParam ?
|
|
84049
|
+
const coverageScope = (col, srcCol) => sourceIdParam ? and61(eq70(col, project.id), eq70(srcCol, sourceIdParam)) : eq70(col, project.id);
|
|
83923
84050
|
const firsts = [
|
|
83924
84051
|
app.db.select({ v: sql28`MIN(${crawlerEventsHourly.tsHour})` }).from(crawlerEventsHourly).where(coverageScope(crawlerEventsHourly.projectId, crawlerEventsHourly.sourceId)).get()?.v,
|
|
83925
84052
|
app.db.select({ v: sql28`MIN(${aiUserFetchEventsHourly.tsHour})` }).from(aiUserFetchEventsHourly).where(coverageScope(aiUserFetchEventsHourly.projectId, aiUserFetchEventsHourly.sourceId)).get()?.v,
|
|
@@ -83997,7 +84124,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83997
84124
|
}
|
|
83998
84125
|
|
|
83999
84126
|
// ../api-routes/src/doctor/checks/report-sweeps.ts
|
|
84000
|
-
import { and as
|
|
84127
|
+
import { and as and62, asc as asc15, eq as eq71, gte as gte17, lte as lte18 } from "drizzle-orm";
|
|
84001
84128
|
|
|
84002
84129
|
// ../api-routes/src/visibility-attribution.ts
|
|
84003
84130
|
function buildQueryAttribution2(projectQueries) {
|
|
@@ -84031,11 +84158,11 @@ function inspectSweep(ctx, run) {
|
|
|
84031
84158
|
answerMentioned: querySnapshots.answerMentioned,
|
|
84032
84159
|
answerText: querySnapshots.answerText,
|
|
84033
84160
|
measurementExecutionId: querySnapshots.measurementExecutionId
|
|
84034
|
-
}).from(querySnapshots).where(
|
|
84161
|
+
}).from(querySnapshots).where(eq71(querySnapshots.runId, run.id)).all();
|
|
84035
84162
|
if (snapshots.length === 0) return { detail: { ...base, complete: false, reason: "no-answers" } };
|
|
84036
84163
|
try {
|
|
84037
84164
|
if (run.measurementPlanVersionId) {
|
|
84038
|
-
const version = ctx.db.select().from(measurementPlanVersions).where(
|
|
84165
|
+
const version = ctx.db.select().from(measurementPlanVersions).where(and62(eq71(measurementPlanVersions.projectId, ctx.project.id), eq71(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
|
|
84039
84166
|
if (!version) return { detail: { ...base, complete: false, reason: "missing-plan" } };
|
|
84040
84167
|
const plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
|
|
84041
84168
|
const manifest = measurementRunExpectedSlots(run, plan);
|
|
@@ -84067,10 +84194,10 @@ function inspectSweep(ctx, run) {
|
|
|
84067
84194
|
};
|
|
84068
84195
|
return unbound ? { detail } : { detail, coverage: slotCoverage(expected2, answered3) };
|
|
84069
84196
|
}
|
|
84070
|
-
const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(
|
|
84197
|
+
const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq71(simpleMeasurementDefinitions.runId, run.id)).get();
|
|
84071
84198
|
const frozen = stored ? simpleMeasurementDefinitionSchema.parse(stored.definition) : null;
|
|
84072
|
-
const project = ctx.db.select().from(projects).where(
|
|
84073
|
-
const basket = frozen ? frozen.queries.map((query) => ({ id: query.queryId, query: query.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(
|
|
84199
|
+
const project = ctx.db.select().from(projects).where(eq71(projects.id, ctx.project.id)).get();
|
|
84200
|
+
const basket = frozen ? frozen.queries.map((query) => ({ id: query.queryId, query: query.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq71(queries.projectId, project.id)).all();
|
|
84074
84201
|
const providers = frozen ? frozen.engines.map((engine) => engine.provider) : project.providers.length > 0 ? project.providers : [...new Set(snapshots.map((snapshot) => snapshot.provider))];
|
|
84075
84202
|
const attribution = buildQueryAttribution2(basket);
|
|
84076
84203
|
const expected = new Set(basket.flatMap((query) => providers.map((provider) => JSON.stringify([query.id, provider.trim().toLowerCase()]))));
|
|
@@ -84119,7 +84246,7 @@ var reportSweepsCheck = {
|
|
|
84119
84246
|
const graded = gradedReportMonths(reportMonths, now);
|
|
84120
84247
|
const months = reportMonths.map((month) => {
|
|
84121
84248
|
const bounds = calendarMonthBounds(month);
|
|
84122
|
-
const monthRuns = ctx.db.select().from(runs).where(
|
|
84249
|
+
const monthRuns = ctx.db.select().from(runs).where(and62(eq71(runs.projectId, ctx.project.id), eq71(runs.kind, RunKinds["answer-visibility"]), notProbeRun(), gte17(runs.createdAt, bounds.since), lte18(runs.createdAt, bounds.until))).orderBy(asc15(runs.createdAt), asc15(runs.id)).all();
|
|
84123
84250
|
const evidence = monthRuns.map((run) => inspectSweep(ctx, run));
|
|
84124
84251
|
return {
|
|
84125
84252
|
month,
|
|
@@ -84142,7 +84269,7 @@ var reportSweepsCheck = {
|
|
|
84142
84269
|
};
|
|
84143
84270
|
|
|
84144
84271
|
// ../api-routes/src/doctor/checks/report-models.ts
|
|
84145
|
-
import { and as
|
|
84272
|
+
import { and as and63, eq as eq72, gte as gte18, inArray as inArray29, lte as lte19 } from "drizzle-orm";
|
|
84146
84273
|
var reportModelsCheck = {
|
|
84147
84274
|
id: "report.models",
|
|
84148
84275
|
category: CheckCategories.providers,
|
|
@@ -84157,7 +84284,7 @@ var reportModelsCheck = {
|
|
|
84157
84284
|
const months = reportMonths.flatMap((month) => {
|
|
84158
84285
|
const bounds = calendarMonthBounds(month);
|
|
84159
84286
|
const previous = new Date(Date.parse(bounds.since) - 1).toISOString().slice(0, 7);
|
|
84160
|
-
const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs,
|
|
84287
|
+
const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq72(runs.id, querySnapshots.runId)).where(and63(eq72(runs.projectId, ctx.project.id), eq72(runs.kind, RunKinds["answer-visibility"]), inArray29(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte18(runs.createdAt, bounds.since), lte19(runs.createdAt, bounds.until))).all();
|
|
84161
84288
|
return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
|
|
84162
84289
|
month,
|
|
84163
84290
|
previousMonth: previous,
|
|
@@ -84198,7 +84325,7 @@ var reportModelsCheck = {
|
|
|
84198
84325
|
};
|
|
84199
84326
|
|
|
84200
84327
|
// ../api-routes/src/doctor/checks/report-daily-data.ts
|
|
84201
|
-
import { eq as
|
|
84328
|
+
import { eq as eq73 } from "drizzle-orm";
|
|
84202
84329
|
var REPORTING_LAG_DAYS = 3;
|
|
84203
84330
|
function dateSpan(dates) {
|
|
84204
84331
|
const sorted = [...dates].sort();
|
|
@@ -84207,31 +84334,31 @@ function dateSpan(dates) {
|
|
|
84207
84334
|
function latestSyncRange(ctx, source) {
|
|
84208
84335
|
const projectId = ctx.project.id;
|
|
84209
84336
|
if (source === "ga") {
|
|
84210
|
-
const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(
|
|
84337
|
+
const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq73(gaDailyTotals.projectId, projectId)).all();
|
|
84211
84338
|
const latest2 = rows2.reduce((max2, row) => max2 === null || row.syncedAt > max2 ? row.syncedAt : max2, null);
|
|
84212
|
-
const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
84339
|
+
const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq73(gaTrafficSummaries.projectId, projectId)).get();
|
|
84213
84340
|
if (summary3 && (latest2 === null || summary3.syncedAt >= latest2)) return { start: summary3.start, end: summary3.end, syncedAt: summary3.syncedAt };
|
|
84214
84341
|
const span2 = dateSpan(rows2.filter((row) => row.syncedAt === latest2).map((row) => row.date));
|
|
84215
84342
|
return span2 && latest2 !== null ? { ...span2, syncedAt: latest2 } : null;
|
|
84216
84343
|
}
|
|
84217
|
-
const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(
|
|
84344
|
+
const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq73(gscDailyTotals.projectId, projectId)).all();
|
|
84218
84345
|
const latest = rows.reduce((max2, row) => max2 === null || row.createdAt > max2 ? row.createdAt : max2, null);
|
|
84219
84346
|
const span = dateSpan(rows.filter((row) => row.createdAt === latest).map((row) => row.date));
|
|
84220
84347
|
if (!span || latest === null) return null;
|
|
84221
|
-
const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(
|
|
84348
|
+
const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq73(gscDataWatermarks.projectId, projectId)).get();
|
|
84222
84349
|
return watermark?.through && watermark.updatedAt === latest && watermark.through > span.end ? { start: span.start, end: watermark.through, syncedAt: latest } : { ...span, syncedAt: latest };
|
|
84223
84350
|
}
|
|
84224
84351
|
function dailyCoverage(ctx, month, source) {
|
|
84225
84352
|
const project = ctx.project;
|
|
84226
84353
|
const connection = source === "ga" ? ctx.ga4CredentialStore?.getConnection(project.name) ?? ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4") : ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "gsc");
|
|
84227
|
-
const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(
|
|
84354
|
+
const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(eq73(gaDailyTotals.projectId, project.id)).all() : ctx.db.select({ date: gscDailyTotals.date, count: gscDailyTotals.impressions }).from(gscDailyTotals).where(eq73(gscDailyTotals.projectId, project.id)).all();
|
|
84228
84355
|
if (!connection && stored.length === 0) return null;
|
|
84229
84356
|
const bounds = calendarMonthBounds(month);
|
|
84230
84357
|
const start = bounds.since.slice(0, 10);
|
|
84231
84358
|
const end = bounds.until.slice(0, 10);
|
|
84232
84359
|
const timeZone = source === "gsc" ? "America/Los_Angeles" : "UTC";
|
|
84233
84360
|
const matureThrough = isoDateDaysBeforeInTimeZone((/* @__PURE__ */ new Date()).toISOString(), REPORTING_LAG_DAYS, timeZone);
|
|
84234
|
-
const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(
|
|
84361
|
+
const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq73(projects.id, project.id)).get().date.slice(0, 10);
|
|
84235
84362
|
const connectedAt = connection?.createdAt?.slice(0, 10) ?? created;
|
|
84236
84363
|
const onboardingDate = [connectedAt, ...stored.map((row) => row.date)].sort()[0];
|
|
84237
84364
|
const byDate = new Map(stored.map((row) => [row.date, row.count]));
|
|
@@ -84313,7 +84440,7 @@ var reportDailyDataCheck = {
|
|
|
84313
84440
|
};
|
|
84314
84441
|
|
|
84315
84442
|
// ../api-routes/src/doctor/checks/referral-assessment.ts
|
|
84316
|
-
import { and as
|
|
84443
|
+
import { and as and64, eq as eq74, ne as ne14 } from "drizzle-orm";
|
|
84317
84444
|
var REFERRAL_ASSESSMENT_CHECKS = [{
|
|
84318
84445
|
id: "report.ai-referral-bursts",
|
|
84319
84446
|
category: CheckCategories.integrations,
|
|
@@ -84322,7 +84449,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
|
|
|
84322
84449
|
notificationPolicy: CheckNotificationPolicies.silent,
|
|
84323
84450
|
run: (ctx) => {
|
|
84324
84451
|
if (!ctx.project) return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.no-project", summary: "Select a project to inspect referral evidence." };
|
|
84325
|
-
const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(
|
|
84452
|
+
const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and64(eq74(trafficSources.projectId, ctx.project.id), ne14(trafficSources.status, TrafficSourceStatuses.archived))).get();
|
|
84326
84453
|
if (!configured) {
|
|
84327
84454
|
return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.not-configured", summary: "No non-archived server traffic source is configured for this project." };
|
|
84328
84455
|
}
|
|
@@ -84682,7 +84809,6 @@ var skillsTriggerSurfaceCheck = {
|
|
|
84682
84809
|
var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck, skillsTriggerSurfaceCheck];
|
|
84683
84810
|
|
|
84684
84811
|
// ../api-routes/src/doctor/checks/backlinks.ts
|
|
84685
|
-
import { and as and64, eq as eq74 } from "drizzle-orm";
|
|
84686
84812
|
function skippedNoProject() {
|
|
84687
84813
|
return {
|
|
84688
84814
|
status: CheckStatuses.skipped,
|
|
@@ -84698,36 +84824,58 @@ var BACKLINKS_CHECKS = [
|
|
|
84698
84824
|
title: "Backlinks source connected",
|
|
84699
84825
|
run: (ctx) => {
|
|
84700
84826
|
if (!ctx.project) return skippedNoProject();
|
|
84701
|
-
const
|
|
84702
|
-
const
|
|
84703
|
-
|
|
84704
|
-
|
|
84705
|
-
|
|
84706
|
-
|
|
84827
|
+
const state = readCommonCrawlSourceState(ctx.db, ctx.project.id);
|
|
84828
|
+
const details = {
|
|
84829
|
+
commoncrawl: state.connected,
|
|
84830
|
+
commoncrawlReadyRelease: state.hasReadyRelease,
|
|
84831
|
+
commoncrawlHasData: state.hasProjectData
|
|
84832
|
+
};
|
|
84833
|
+
if (!state.hasReadyRelease) {
|
|
84707
84834
|
return {
|
|
84708
84835
|
status: CheckStatuses.warn,
|
|
84709
84836
|
code: "backlinks.source.none",
|
|
84710
|
-
summary: `No backlink source is set up for ${ctx.project.name}.`,
|
|
84711
|
-
remediation: "
|
|
84712
|
-
details
|
|
84837
|
+
summary: `No backlink source is set up for ${ctx.project.name}: no Common Crawl release has been synced.`,
|
|
84838
|
+
remediation: "Run `canonry backlinks sync` to download the newest Common Crawl release for the workspace.",
|
|
84839
|
+
details
|
|
84713
84840
|
};
|
|
84714
84841
|
}
|
|
84715
|
-
|
|
84716
|
-
|
|
84717
|
-
|
|
84718
|
-
|
|
84842
|
+
if (!state.hasProjectData) {
|
|
84843
|
+
return {
|
|
84844
|
+
status: CheckStatuses.warn,
|
|
84845
|
+
code: "backlinks.source.none",
|
|
84846
|
+
summary: state.hasCurrentReadyRelease ? `A Common Crawl release is synced, but no backlinks have been extracted for ${ctx.project.name} yet.` : `No backlinks have been extracted for ${ctx.project.name} yet, and no Common Crawl release is currently ready.`,
|
|
84847
|
+
remediation: state.hasCurrentReadyRelease ? `Run \`canonry backlinks extract ${ctx.project.name}\` to extract this project's backlinks from the synced release.` : "Run `canonry backlinks sync` to make a Common Crawl release ready and extract the workspace projects.",
|
|
84848
|
+
details
|
|
84849
|
+
};
|
|
84850
|
+
}
|
|
84851
|
+
const connected = [BacklinkSources.commoncrawl];
|
|
84719
84852
|
return {
|
|
84720
84853
|
status: CheckStatuses.ok,
|
|
84721
84854
|
code: "backlinks.source.connected",
|
|
84722
84855
|
summary: "Common Crawl backlink source is set up.",
|
|
84723
|
-
remediation:
|
|
84724
|
-
details: {
|
|
84856
|
+
remediation: null,
|
|
84857
|
+
details: { ...details, connected }
|
|
84725
84858
|
};
|
|
84726
84859
|
}
|
|
84727
84860
|
}
|
|
84728
84861
|
];
|
|
84729
84862
|
|
|
84730
84863
|
// ../api-routes/src/doctor/checks/bing-auth.ts
|
|
84864
|
+
import { and as and65, desc as desc36, eq as eq75, inArray as inArray30 } from "drizzle-orm";
|
|
84865
|
+
var BING_CONNECTION_ENTITY = "bing_connection";
|
|
84866
|
+
var BING_CONNECTED_ACTION = "bing.connected";
|
|
84867
|
+
var BING_DISCONNECTED_ACTION = "bing.disconnected";
|
|
84868
|
+
function lostBingConnection(ctx, project) {
|
|
84869
|
+
const hasRow = (row) => row !== void 0;
|
|
84870
|
+
const storedData = hasRow(ctx.db.select({ id: bingCoverageSnapshots.id }).from(bingCoverageSnapshots).where(eq75(bingCoverageSnapshots.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingUrlInspections.id }).from(bingUrlInspections).where(eq75(bingUrlInspections.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingKeywordStats.id }).from(bingKeywordStats).where(eq75(bingKeywordStats.projectId, project.id)).limit(1).get());
|
|
84871
|
+
if (!storedData) return false;
|
|
84872
|
+
const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and65(
|
|
84873
|
+
eq75(auditLog.entityType, BING_CONNECTION_ENTITY),
|
|
84874
|
+
eq75(auditLog.entityId, project.canonicalDomain),
|
|
84875
|
+
inArray30(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
|
|
84876
|
+
)).orderBy(desc36(auditLog.createdAt)).limit(1).get();
|
|
84877
|
+
return lastChange?.action !== BING_DISCONNECTED_ACTION;
|
|
84878
|
+
}
|
|
84731
84879
|
var BING_AUTH_CHECKS = [
|
|
84732
84880
|
{
|
|
84733
84881
|
id: "bing.auth.connection",
|
|
@@ -84754,10 +84902,18 @@ var BING_AUTH_CHECKS = [
|
|
|
84754
84902
|
}
|
|
84755
84903
|
const conn = store.getConnection(ctx.project.canonicalDomain);
|
|
84756
84904
|
if (!conn) {
|
|
84905
|
+
if (!lostBingConnection(ctx, ctx.project)) {
|
|
84906
|
+
return {
|
|
84907
|
+
status: CheckStatuses.skipped,
|
|
84908
|
+
code: "bing.auth.not-connected",
|
|
84909
|
+
summary: `${ctx.project.name} has not connected Bing Webmaster Tools.`,
|
|
84910
|
+
remediation: `Optional: run \`canonry bing connect ${ctx.project.name} --api-key <key>\` to track Bing indexing.`
|
|
84911
|
+
};
|
|
84912
|
+
}
|
|
84757
84913
|
return {
|
|
84758
84914
|
status: CheckStatuses.fail,
|
|
84759
84915
|
code: "bing.auth.no-connection",
|
|
84760
|
-
summary: `No Bing connection for ${ctx.project.canonicalDomain}.`,
|
|
84916
|
+
summary: `No Bing connection for ${ctx.project.canonicalDomain}, but ${ctx.project.name} has stored Bing data from an earlier connection.`,
|
|
84761
84917
|
remediation: `Run \`canonry bing connect ${ctx.project.name} --api-key <key>\` to authorize.`
|
|
84762
84918
|
};
|
|
84763
84919
|
}
|
|
@@ -84874,7 +85030,7 @@ var BING_AUTH_CHECKS = [
|
|
|
84874
85030
|
];
|
|
84875
85031
|
|
|
84876
85032
|
// ../api-routes/src/doctor/checks/content.ts
|
|
84877
|
-
import { eq as
|
|
85033
|
+
import { eq as eq76 } from "drizzle-orm";
|
|
84878
85034
|
var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
|
|
84879
85035
|
var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
|
|
84880
85036
|
function skippedNoProject2() {
|
|
@@ -84887,13 +85043,17 @@ function skippedNoProject2() {
|
|
|
84887
85043
|
}
|
|
84888
85044
|
function loadProject(ctx) {
|
|
84889
85045
|
if (!ctx.project) return null;
|
|
84890
|
-
return ctx.db.select().from(projects).where(
|
|
85046
|
+
return ctx.db.select().from(projects).where(eq76(projects.id, ctx.project.id)).get() ?? null;
|
|
84891
85047
|
}
|
|
84892
85048
|
var winnabilityCoverageCheck = {
|
|
84893
85049
|
id: "content.winnability.coverage",
|
|
84894
85050
|
category: CheckCategories.integrations,
|
|
84895
85051
|
scope: CheckScopes.project,
|
|
84896
85052
|
title: "Content winnability classification coverage",
|
|
85053
|
+
// Advice about content targeting, not a broken instrument. Most projects sit
|
|
85054
|
+
// below the threshold for good, and a check that never clears keeps the
|
|
85055
|
+
// project permanently degraded, so no recovery could ever be sent.
|
|
85056
|
+
notificationPolicy: CheckNotificationPolicies.silent,
|
|
84897
85057
|
run: (ctx) => {
|
|
84898
85058
|
if (!ctx.project) return skippedNoProject2();
|
|
84899
85059
|
const project = loadProject(ctx);
|
|
@@ -84976,7 +85136,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
|
|
|
84976
85136
|
);
|
|
84977
85137
|
|
|
84978
85138
|
// ../api-routes/src/doctor/checks/ads.ts
|
|
84979
|
-
import { eq as
|
|
85139
|
+
import { eq as eq77 } from "drizzle-orm";
|
|
84980
85140
|
var RECENT_SYNC_WARN_DAYS = 7;
|
|
84981
85141
|
var RECENT_SYNC_FAIL_DAYS = 30;
|
|
84982
85142
|
var adsConnectionCheck = {
|
|
@@ -84993,7 +85153,7 @@ var adsConnectionCheck = {
|
|
|
84993
85153
|
remediation: null
|
|
84994
85154
|
};
|
|
84995
85155
|
}
|
|
84996
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
85156
|
+
const row = ctx.db.select().from(adsConnections).where(eq77(adsConnections.projectId, ctx.project.id)).get();
|
|
84997
85157
|
if (!row) {
|
|
84998
85158
|
return {
|
|
84999
85159
|
status: CheckStatuses.skipped,
|
|
@@ -85043,7 +85203,7 @@ var adsRecentSyncCheck = {
|
|
|
85043
85203
|
remediation: null
|
|
85044
85204
|
};
|
|
85045
85205
|
}
|
|
85046
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
85206
|
+
const row = ctx.db.select().from(adsConnections).where(eq77(adsConnections.projectId, ctx.project.id)).get();
|
|
85047
85207
|
if (!row) {
|
|
85048
85208
|
return {
|
|
85049
85209
|
status: CheckStatuses.skipped,
|
|
@@ -85253,7 +85413,7 @@ var ga4ConnectionCheck = {
|
|
|
85253
85413
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
85254
85414
|
|
|
85255
85415
|
// ../api-routes/src/doctor/checks/data-freshness.ts
|
|
85256
|
-
import { and as
|
|
85416
|
+
import { and as and66, desc as desc37, eq as eq78, inArray as inArray31 } from "drizzle-orm";
|
|
85257
85417
|
var GA_DATA_AGING_DAYS = 3;
|
|
85258
85418
|
var GA_DATA_STALE_DAYS = 5;
|
|
85259
85419
|
var GSC_DATA_AGING_DAYS = 5;
|
|
@@ -85281,7 +85441,7 @@ function skipped(code, summary3) {
|
|
|
85281
85441
|
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
85282
85442
|
}
|
|
85283
85443
|
function lastSyncAgeDays(ctx, kinds) {
|
|
85284
|
-
const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(
|
|
85444
|
+
const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and66(eq78(runs.projectId, ctx.project.id), inArray31(runs.kind, kinds), eq78(runs.status, "completed"))).orderBy(desc37(runs.createdAt)).limit(1).get();
|
|
85285
85445
|
if (!row?.createdAt) return null;
|
|
85286
85446
|
const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS2);
|
|
85287
85447
|
return Number.isFinite(age) ? age : null;
|
|
@@ -85377,7 +85537,7 @@ var gaRecentDataCheck = {
|
|
|
85377
85537
|
const connection = gaConnected(ctx);
|
|
85378
85538
|
if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
|
|
85379
85539
|
if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
|
|
85380
|
-
const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(
|
|
85540
|
+
const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq78(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc37(gaDailyTotals.date)).limit(1).get();
|
|
85381
85541
|
return grade(ctx, GA_SPEC, newest?.date);
|
|
85382
85542
|
}
|
|
85383
85543
|
};
|
|
@@ -85392,9 +85552,9 @@ var gscRecentDataCheck = {
|
|
|
85392
85552
|
if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
|
|
85393
85553
|
return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
|
|
85394
85554
|
}
|
|
85395
|
-
const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(
|
|
85555
|
+
const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq78(gscDataWatermarks.projectId, ctx.project.id)).get();
|
|
85396
85556
|
if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
|
|
85397
|
-
const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(
|
|
85557
|
+
const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq78(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc37(gscDailyTotals.date)).limit(1).get();
|
|
85398
85558
|
return grade(ctx, GSC_SPEC, newest?.date);
|
|
85399
85559
|
}
|
|
85400
85560
|
};
|
|
@@ -85662,7 +85822,7 @@ var siteReachabilityCheck = {
|
|
|
85662
85822
|
var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
|
|
85663
85823
|
|
|
85664
85824
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
85665
|
-
import { and as
|
|
85825
|
+
import { and as and67, eq as eq79 } from "drizzle-orm";
|
|
85666
85826
|
var RECENT_SYNC_WARN_DAYS2 = 4;
|
|
85667
85827
|
var RECENT_SYNC_FAIL_DAYS2 = 30;
|
|
85668
85828
|
function skippedNoProject3() {
|
|
@@ -85681,10 +85841,25 @@ function storeUnavailable() {
|
|
|
85681
85841
|
remediation: null
|
|
85682
85842
|
};
|
|
85683
85843
|
}
|
|
85844
|
+
function hasSelectedLocation(ctx, projectId) {
|
|
85845
|
+
return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and67(eq79(gbpLocations.projectId, projectId), eq79(gbpLocations.selected, true))).limit(1).get();
|
|
85846
|
+
}
|
|
85684
85847
|
async function resolveGbpToken(ctx) {
|
|
85685
85848
|
if (!ctx.project) return { ok: false, output: skippedNoProject3() };
|
|
85686
85849
|
const store = ctx.googleConnectionStore;
|
|
85687
85850
|
if (!store) return { ok: false, output: storeUnavailable() };
|
|
85851
|
+
const conn = store.getConnection(ctx.project.canonicalDomain, "gbp");
|
|
85852
|
+
if (!conn && !hasSelectedLocation(ctx, ctx.project.id)) {
|
|
85853
|
+
return {
|
|
85854
|
+
ok: false,
|
|
85855
|
+
output: {
|
|
85856
|
+
status: CheckStatuses.skipped,
|
|
85857
|
+
code: "gbp.auth.not-connected",
|
|
85858
|
+
summary: `${ctx.project.name} has not connected Google Business Profile.`,
|
|
85859
|
+
remediation: `Optional: run \`canonry gbp connect ${ctx.project.name}\` to track a Business Profile.`
|
|
85860
|
+
}
|
|
85861
|
+
};
|
|
85862
|
+
}
|
|
85688
85863
|
const auth = ctx.getGoogleAuthConfig?.() ?? {};
|
|
85689
85864
|
if (!auth.clientId || !auth.clientSecret) {
|
|
85690
85865
|
return {
|
|
@@ -85697,14 +85872,13 @@ async function resolveGbpToken(ctx) {
|
|
|
85697
85872
|
}
|
|
85698
85873
|
};
|
|
85699
85874
|
}
|
|
85700
|
-
const conn = store.getConnection(ctx.project.canonicalDomain, "gbp");
|
|
85701
85875
|
if (!conn) {
|
|
85702
85876
|
return {
|
|
85703
85877
|
ok: false,
|
|
85704
85878
|
output: {
|
|
85705
85879
|
status: CheckStatuses.fail,
|
|
85706
85880
|
code: "gbp.auth.no-connection",
|
|
85707
|
-
summary: `No Google Business Profile connection for ${ctx.project.canonicalDomain}.`,
|
|
85881
|
+
summary: `No Google Business Profile connection for ${ctx.project.canonicalDomain}, but ${ctx.project.name} still has selected locations.`,
|
|
85708
85882
|
remediation: `Run \`canonry gbp connect ${ctx.project.name}\` to authorize.`
|
|
85709
85883
|
}
|
|
85710
85884
|
};
|
|
@@ -85918,7 +86092,7 @@ var recentSyncCheck = {
|
|
|
85918
86092
|
title: "GBP recent sync",
|
|
85919
86093
|
run: (ctx) => {
|
|
85920
86094
|
if (!ctx.project) return skippedNoProject3();
|
|
85921
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
86095
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and67(eq79(gbpLocations.projectId, ctx.project.id), eq79(gbpLocations.selected, true))).all();
|
|
85922
86096
|
if (selected.length === 0) {
|
|
85923
86097
|
return {
|
|
85924
86098
|
status: CheckStatuses.skipped,
|
|
@@ -85978,7 +86152,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
85978
86152
|
);
|
|
85979
86153
|
|
|
85980
86154
|
// ../api-routes/src/doctor/checks/places.ts
|
|
85981
|
-
import { eq as
|
|
86155
|
+
import { eq as eq80 } from "drizzle-orm";
|
|
85982
86156
|
var apiKeyCheck = {
|
|
85983
86157
|
id: "gbp.places.api-key",
|
|
85984
86158
|
category: CheckCategories.auth,
|
|
@@ -86023,7 +86197,7 @@ var apiKeyCheck = {
|
|
|
86023
86197
|
details: { tier: cfg.tier }
|
|
86024
86198
|
};
|
|
86025
86199
|
}
|
|
86026
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
86200
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq80(gbpLocations.projectId, ctx.project.id)).all();
|
|
86027
86201
|
const selected = rows.filter((r) => r.selected);
|
|
86028
86202
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
86029
86203
|
const details = {
|
|
@@ -86056,7 +86230,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
|
|
|
86056
86230
|
);
|
|
86057
86231
|
|
|
86058
86232
|
// ../api-routes/src/doctor/checks/gbp-reviews.ts
|
|
86059
|
-
import { and as
|
|
86233
|
+
import { and as and68, eq as eq81 } from "drizzle-orm";
|
|
86060
86234
|
var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
|
|
86061
86235
|
var V4_ACCESS_REMEDIATION = "Business Profile reviews use the legacy My Business API (mybusiness.googleapis.com), which Google enables per Cloud project only on request; the API Library and `gcloud services enable` cannot turn it on. Open the enable link in the API access approval email as the approved account, or reply to the access-request thread asking Google to enable mybusiness.googleapis.com for your project number, then run `canonry gbp sync`.";
|
|
86062
86236
|
var reviewsAccessCheck = {
|
|
@@ -86084,7 +86258,7 @@ var reviewsAccessCheck = {
|
|
|
86084
86258
|
reviewsAccess: gbpLocations.reviewsAccess,
|
|
86085
86259
|
reviewsAccessReason: gbpLocations.reviewsAccessReason,
|
|
86086
86260
|
reviewsCheckedAt: gbpLocations.reviewsCheckedAt
|
|
86087
|
-
}).from(gbpLocations).where(
|
|
86261
|
+
}).from(gbpLocations).where(and68(eq81(gbpLocations.projectId, project.id), eq81(gbpLocations.selected, true))).all();
|
|
86088
86262
|
if (locations.length === 0) {
|
|
86089
86263
|
return {
|
|
86090
86264
|
status: CheckStatuses.skipped,
|
|
@@ -86104,7 +86278,7 @@ var reviewsAccessCheck = {
|
|
|
86104
86278
|
const places = ctx.getPlacesConfig?.();
|
|
86105
86279
|
const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
|
|
86106
86280
|
const v4Worked = new Set(
|
|
86107
|
-
ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(
|
|
86281
|
+
ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and68(eq81(gbpReviewRatings.projectId, project.id), eq81(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
|
|
86108
86282
|
);
|
|
86109
86283
|
const coverageOf = (loc) => {
|
|
86110
86284
|
if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
|
|
@@ -86138,7 +86312,7 @@ var reviewsAccessCheck = {
|
|
|
86138
86312
|
none > 0 ? `${none} have no review source` : null
|
|
86139
86313
|
].filter((part) => part !== null);
|
|
86140
86314
|
const summary3 = `Business Profile reviews are not available${why}. ${parts.join("; ")}.`;
|
|
86141
|
-
const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(
|
|
86315
|
+
const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq81(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
|
|
86142
86316
|
const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
|
|
86143
86317
|
if (none > 0 && subscribed) {
|
|
86144
86318
|
return {
|
|
@@ -86696,7 +86870,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
86696
86870
|
];
|
|
86697
86871
|
|
|
86698
86872
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
86699
|
-
import { and as
|
|
86873
|
+
import { and as and69, eq as eq82, gte as gte19, inArray as inArray32, ne as ne15, sql as sql29 } from "drizzle-orm";
|
|
86700
86874
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
86701
86875
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
86702
86876
|
function isCloudflareDirectPush(source) {
|
|
@@ -86735,8 +86909,8 @@ function skippedNoProject5() {
|
|
|
86735
86909
|
function loadProbes(ctx) {
|
|
86736
86910
|
if (!ctx.project) return [];
|
|
86737
86911
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
86738
|
-
|
|
86739
|
-
|
|
86912
|
+
and69(
|
|
86913
|
+
eq82(trafficSources.projectId, ctx.project.id),
|
|
86740
86914
|
ne15(trafficSources.status, TrafficSourceStatuses.archived)
|
|
86741
86915
|
)
|
|
86742
86916
|
).all();
|
|
@@ -86842,27 +87016,27 @@ var recentDataCheck = {
|
|
|
86842
87016
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
86843
87017
|
const recentCrawlers = Number(
|
|
86844
87018
|
ctx.db.select({ total: sql29`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
86845
|
-
|
|
86846
|
-
|
|
86847
|
-
|
|
87019
|
+
and69(
|
|
87020
|
+
eq82(crawlerEventsHourly.projectId, ctx.project.id),
|
|
87021
|
+
inArray32(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
86848
87022
|
gte19(crawlerEventsHourly.tsHour, warnCutoff)
|
|
86849
87023
|
)
|
|
86850
87024
|
).get()?.total ?? 0
|
|
86851
87025
|
);
|
|
86852
87026
|
const recentReferrals = Number(
|
|
86853
87027
|
ctx.db.select({ total: sql29`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
86854
|
-
|
|
86855
|
-
|
|
86856
|
-
|
|
87028
|
+
and69(
|
|
87029
|
+
eq82(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
87030
|
+
inArray32(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
86857
87031
|
gte19(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
86858
87032
|
)
|
|
86859
87033
|
).get()?.total ?? 0
|
|
86860
87034
|
);
|
|
86861
87035
|
const recentUserFetches = Number(
|
|
86862
87036
|
ctx.db.select({ total: sql29`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
86863
|
-
|
|
86864
|
-
|
|
86865
|
-
|
|
87037
|
+
and69(
|
|
87038
|
+
eq82(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
87039
|
+
inArray32(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
86866
87040
|
gte19(aiUserFetchEventsHourly.tsHour, warnCutoff)
|
|
86867
87041
|
)
|
|
86868
87042
|
).get()?.total ?? 0
|
|
@@ -86886,27 +87060,27 @@ var recentDataCheck = {
|
|
|
86886
87060
|
}
|
|
86887
87061
|
const olderCrawlers = Number(
|
|
86888
87062
|
ctx.db.select({ total: sql29`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
86889
|
-
|
|
86890
|
-
|
|
86891
|
-
|
|
87063
|
+
and69(
|
|
87064
|
+
eq82(crawlerEventsHourly.projectId, ctx.project.id),
|
|
87065
|
+
inArray32(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
86892
87066
|
gte19(crawlerEventsHourly.tsHour, failCutoff)
|
|
86893
87067
|
)
|
|
86894
87068
|
).get()?.total ?? 0
|
|
86895
87069
|
);
|
|
86896
87070
|
const olderReferrals = Number(
|
|
86897
87071
|
ctx.db.select({ total: sql29`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
86898
|
-
|
|
86899
|
-
|
|
86900
|
-
|
|
87072
|
+
and69(
|
|
87073
|
+
eq82(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
87074
|
+
inArray32(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
86901
87075
|
gte19(aiReferralEventsHourly.tsHour, failCutoff)
|
|
86902
87076
|
)
|
|
86903
87077
|
).get()?.total ?? 0
|
|
86904
87078
|
);
|
|
86905
87079
|
const olderUserFetches = Number(
|
|
86906
87080
|
ctx.db.select({ total: sql29`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
86907
|
-
|
|
86908
|
-
|
|
86909
|
-
|
|
87081
|
+
and69(
|
|
87082
|
+
eq82(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
87083
|
+
inArray32(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
86910
87084
|
gte19(aiUserFetchEventsHourly.tsHour, failCutoff)
|
|
86911
87085
|
)
|
|
86912
87086
|
).get()?.total ?? 0
|
|
@@ -87080,6 +87254,7 @@ var cacheBlindSpotCheck = {
|
|
|
87080
87254
|
category: CheckCategories.integrations,
|
|
87081
87255
|
scope: CheckScopes.project,
|
|
87082
87256
|
title: "WordPress traffic cache blind spot",
|
|
87257
|
+
notificationPolicy: CheckNotificationPolicies.silent,
|
|
87083
87258
|
run: (ctx) => {
|
|
87084
87259
|
if (!ctx.project) return skippedNoProject5();
|
|
87085
87260
|
const wpSources = loadProbes(ctx).filter(
|
|
@@ -87679,7 +87854,7 @@ var GOOGLE_MARKETING_DOCTOR_CHECKS = createGoogleMarketingDoctorChecks(
|
|
|
87679
87854
|
);
|
|
87680
87855
|
|
|
87681
87856
|
// ../api-routes/src/doctor/checks/integration-sync.ts
|
|
87682
|
-
import { and as
|
|
87857
|
+
import { and as and70, count as count3, desc as desc38, eq as eq83, gt as gt7, gte as gte20, inArray as inArray33, isNull as isNull14, notInArray as notInArray2, or as or19 } from "drizzle-orm";
|
|
87683
87858
|
var SYNC_FAILURE_WINDOW = 3;
|
|
87684
87859
|
var SUMMARY_ERROR_LIMIT = 300;
|
|
87685
87860
|
var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
|
|
@@ -87699,7 +87874,7 @@ function gscConnection(ctx, project) {
|
|
|
87699
87874
|
function gbpConnection(ctx, project) {
|
|
87700
87875
|
if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
|
|
87701
87876
|
if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
|
|
87702
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(
|
|
87877
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and70(eq83(gbpLocations.projectId, project.id), eq83(gbpLocations.selected, true))).limit(1).get();
|
|
87703
87878
|
return selected ? CONNECTED : { state: "no-selected-locations" };
|
|
87704
87879
|
}
|
|
87705
87880
|
function gaConnection(ctx, project) {
|
|
@@ -87777,20 +87952,20 @@ function gradeSyncFailures(ctx, spec) {
|
|
|
87777
87952
|
return skipped3(code("no-selected-locations"), `No ${spec.label} locations are selected for this project.`);
|
|
87778
87953
|
}
|
|
87779
87954
|
const nothingToSync = spec.nothingToSyncErrors ?? [];
|
|
87780
|
-
const scope =
|
|
87781
|
-
|
|
87782
|
-
|
|
87783
|
-
...nothingToSync.length > 0 ? [
|
|
87955
|
+
const scope = and70(
|
|
87956
|
+
eq83(runs.projectId, project.id),
|
|
87957
|
+
eq83(runs.kind, spec.kind),
|
|
87958
|
+
...nothingToSync.length > 0 ? [or19(isNull14(runs.error), notInArray2(runs.error, [...nothingToSync]))] : []
|
|
87784
87959
|
);
|
|
87785
|
-
const graded = connection.since ?
|
|
87786
|
-
const recent = ctx.db.select({ status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(
|
|
87960
|
+
const graded = connection.since ? and70(scope, gte20(runs.createdAt, connection.since)) : scope;
|
|
87961
|
+
const recent = ctx.db.select({ status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and70(graded, inArray33(runs.status, FINISHED_STATUSES))).orderBy(desc38(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
|
|
87787
87962
|
if (recent.length === 0) {
|
|
87788
87963
|
return skipped3(code("no-runs"), `${spec.label} is connected but no sync has finished yet.`);
|
|
87789
87964
|
}
|
|
87790
|
-
const lastSuccess = ctx.db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
87791
|
-
const failuresSinceSuccess = ctx.db.select({ value: count3() }).from(runs).where(
|
|
87965
|
+
const lastSuccess = ctx.db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and70(graded, inArray33(runs.status, SUCCESS_STATUSES))).orderBy(desc38(runs.createdAt)).limit(1).get();
|
|
87966
|
+
const failuresSinceSuccess = ctx.db.select({ value: count3() }).from(runs).where(and70(
|
|
87792
87967
|
graded,
|
|
87793
|
-
|
|
87968
|
+
eq83(runs.status, RunStatuses.failed),
|
|
87794
87969
|
...lastSuccess ? [gt7(runs.createdAt, lastSuccess.createdAt)] : []
|
|
87795
87970
|
)).get()?.value ?? 0;
|
|
87796
87971
|
const lastSuccessAt = lastSuccess ? lastSuccess.finishedAt ?? lastSuccess.createdAt : null;
|
|
@@ -88104,7 +88279,7 @@ async function doctorRoutes(app, opts) {
|
|
|
88104
88279
|
|
|
88105
88280
|
// ../api-routes/src/discovery/routes.ts
|
|
88106
88281
|
import crypto50 from "crypto";
|
|
88107
|
-
import { and as
|
|
88282
|
+
import { and as and71, desc as desc39, eq as eq84, gte as gte21, inArray as inArray34, isNull as isNull15, or as or20 } from "drizzle-orm";
|
|
88108
88283
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
88109
88284
|
async function discoveryRoutes(app, opts) {
|
|
88110
88285
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -88138,13 +88313,13 @@ async function discoveryRoutes(app, opts) {
|
|
|
88138
88313
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
88139
88314
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
88140
88315
|
const decision = app.db.transaction((tx) => {
|
|
88141
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
88142
|
-
|
|
88143
|
-
|
|
88316
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and71(
|
|
88317
|
+
eq84(discoverySessions.projectId, project.id),
|
|
88318
|
+
eq84(discoverySessions.icpDescription, icpDescription),
|
|
88144
88319
|
// Buyer is part of session identity: it changes the seed prompt's
|
|
88145
88320
|
// semantics, so a request with a different (or no) buyer must start
|
|
88146
88321
|
// its own session, never adopt another buyer's probes.
|
|
88147
|
-
parsed.data.buyerDescription == null ? isNull15(discoverySessions.buyerDescription) :
|
|
88322
|
+
parsed.data.buyerDescription == null ? isNull15(discoverySessions.buyerDescription) : eq84(discoverySessions.buyerDescription, parsed.data.buyerDescription),
|
|
88148
88323
|
// Locations are identity too: a different service-area subset seeds
|
|
88149
88324
|
// and probes a different geo, so it must never reuse another geo's
|
|
88150
88325
|
// session. resolveLocations is deterministic (project-config order),
|
|
@@ -88154,18 +88329,18 @@ async function discoveryRoutes(app, opts) {
|
|
|
88154
88329
|
// locations a NULL row's subset is unknowable, so it conservatively
|
|
88155
88330
|
// never reuses — a one-time, bounded (2h window) non-reuse after
|
|
88156
88331
|
// upgrade, never a wrong reuse.
|
|
88157
|
-
locations.length === 0 ?
|
|
88332
|
+
locations.length === 0 ? or20(isNull15(discoverySessions.locations), eq84(discoverySessions.locations, locations)) : eq84(discoverySessions.locations, locations),
|
|
88158
88333
|
// Seed provider set is identity: a different phrasing distribution
|
|
88159
88334
|
// must never reuse another set's session. Null (= Gemini-only
|
|
88160
88335
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
88161
|
-
seedProviders == null ? isNull15(discoverySessions.seedProviders) :
|
|
88162
|
-
|
|
88336
|
+
seedProviders == null ? isNull15(discoverySessions.seedProviders) : eq84(discoverySessions.seedProviders, seedProviders),
|
|
88337
|
+
inArray34(discoverySessions.status, [
|
|
88163
88338
|
DiscoverySessionStatuses.queued,
|
|
88164
88339
|
DiscoverySessionStatuses.seeding,
|
|
88165
88340
|
DiscoverySessionStatuses.probing
|
|
88166
88341
|
]),
|
|
88167
88342
|
gte21(discoverySessions.createdAt, ageFloorIso)
|
|
88168
|
-
)).orderBy(
|
|
88343
|
+
)).orderBy(desc39(discoverySessions.createdAt)).get();
|
|
88169
88344
|
if (existing && existing.runId) {
|
|
88170
88345
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
88171
88346
|
}
|
|
@@ -88234,7 +88409,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88234
88409
|
const project = resolveProject(app.db, request.params.name);
|
|
88235
88410
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
88236
88411
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
88237
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
88412
|
+
const rows = app.db.select().from(discoverySessions).where(eq84(discoverySessions.projectId, project.id)).orderBy(desc39(discoverySessions.createdAt)).limit(limit).all();
|
|
88238
88413
|
return reply.send(rows.map(serializeSession));
|
|
88239
88414
|
}
|
|
88240
88415
|
);
|
|
@@ -88242,11 +88417,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
88242
88417
|
"/projects/:name/discover/sessions/:id",
|
|
88243
88418
|
async (request, reply) => {
|
|
88244
88419
|
const project = resolveProject(app.db, request.params.name);
|
|
88245
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
88420
|
+
const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
|
|
88246
88421
|
if (!session || session.projectId !== project.id) {
|
|
88247
88422
|
throw notFound("Discovery session", request.params.id);
|
|
88248
88423
|
}
|
|
88249
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
88424
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
|
|
88250
88425
|
const detail = {
|
|
88251
88426
|
...serializeSession(session),
|
|
88252
88427
|
probes: probeRows.map(serializeProbe)
|
|
@@ -88258,7 +88433,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88258
88433
|
"/projects/:name/discover/sessions/:id/harvest",
|
|
88259
88434
|
async (request, reply) => {
|
|
88260
88435
|
const project = resolveProject(app.db, request.params.name);
|
|
88261
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
88436
|
+
const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
|
|
88262
88437
|
if (!session || session.projectId !== project.id) {
|
|
88263
88438
|
throw notFound("Discovery session", request.params.id);
|
|
88264
88439
|
}
|
|
@@ -88266,7 +88441,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88266
88441
|
const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
|
|
88267
88442
|
const applyAnchor = request.query.anchor !== "false";
|
|
88268
88443
|
const provider = session.seedProvider ?? "gemini";
|
|
88269
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
88444
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
|
|
88270
88445
|
const extract = opts.harvestSearchQueries;
|
|
88271
88446
|
const probesWithQueries = probeRows.map((row) => {
|
|
88272
88447
|
if (!extract || !row.rawResponse) return { searchQueries: [] };
|
|
@@ -88277,7 +88452,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88277
88452
|
return { searchQueries: [] };
|
|
88278
88453
|
}
|
|
88279
88454
|
});
|
|
88280
|
-
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(
|
|
88455
|
+
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq84(queries.projectId, project.id)).all().map((r) => r.query);
|
|
88281
88456
|
const anchorTerms = buildHarvestAnchorTerms(
|
|
88282
88457
|
[session.icpDescription ?? "", ...trackedQueries],
|
|
88283
88458
|
effectiveDomains(project)
|
|
@@ -88324,11 +88499,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
88324
88499
|
"/projects/:name/discover/sessions/:id/promote",
|
|
88325
88500
|
async (request, reply) => {
|
|
88326
88501
|
const project = resolveProject(app.db, request.params.name);
|
|
88327
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
88502
|
+
const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
|
|
88328
88503
|
if (!session || session.projectId !== project.id) {
|
|
88329
88504
|
throw notFound("Discovery session", request.params.id);
|
|
88330
88505
|
}
|
|
88331
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
88506
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
|
|
88332
88507
|
const cited = /* @__PURE__ */ new Set();
|
|
88333
88508
|
const aspirational = /* @__PURE__ */ new Set();
|
|
88334
88509
|
const wasted = /* @__PURE__ */ new Set();
|
|
@@ -88363,7 +88538,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88363
88538
|
);
|
|
88364
88539
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
88365
88540
|
const project = resolveProject(app.db, request.params.name);
|
|
88366
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
88541
|
+
const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
|
|
88367
88542
|
if (!session || session.projectId !== project.id) {
|
|
88368
88543
|
throw notFound("Discovery session", request.params.id);
|
|
88369
88544
|
}
|
|
@@ -88386,7 +88561,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88386
88561
|
const bucketSet = new Set(buckets);
|
|
88387
88562
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
88388
88563
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
88389
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
88564
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
|
|
88390
88565
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
88391
88566
|
for (const probe of probeRows) {
|
|
88392
88567
|
if (!probe.bucket) continue;
|
|
@@ -88394,7 +88569,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88394
88569
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
88395
88570
|
}
|
|
88396
88571
|
const existingQueries = new Set(
|
|
88397
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
88572
|
+
app.db.select({ query: queries.query }).from(queries).where(eq84(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
88398
88573
|
);
|
|
88399
88574
|
const promotedQueries = [];
|
|
88400
88575
|
const skippedQueries = [];
|
|
@@ -88607,7 +88782,7 @@ function planCompetitorPromotion(input) {
|
|
|
88607
88782
|
|
|
88608
88783
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
88609
88784
|
import crypto51 from "crypto";
|
|
88610
|
-
import { eq as
|
|
88785
|
+
import { eq as eq85 } from "drizzle-orm";
|
|
88611
88786
|
var DEFAULT_MAX_PROBES = 100;
|
|
88612
88787
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
88613
88788
|
function classifyProbeBucket(input) {
|
|
@@ -88692,7 +88867,7 @@ async function executeDiscovery(opts) {
|
|
|
88692
88867
|
status: DiscoverySessionStatuses.seeding,
|
|
88693
88868
|
dedupThreshold,
|
|
88694
88869
|
startedAt
|
|
88695
|
-
}).where(
|
|
88870
|
+
}).where(eq85(discoverySessions.id, opts.sessionId)).run();
|
|
88696
88871
|
const seedResult = await opts.deps.seed({
|
|
88697
88872
|
project: opts.project,
|
|
88698
88873
|
icpDescription: opts.icpDescription,
|
|
@@ -88753,7 +88928,7 @@ async function executeDiscovery(opts) {
|
|
|
88753
88928
|
dedupBandPairFraction: dedupStats.bandPairFraction,
|
|
88754
88929
|
dedupPairsTotal: dedupStats.pairsTotal,
|
|
88755
88930
|
warning
|
|
88756
|
-
}).where(
|
|
88931
|
+
}).where(eq85(discoverySessions.id, opts.sessionId)).run();
|
|
88757
88932
|
const probeLocation = opts.locations?.[0];
|
|
88758
88933
|
const probeResults = await mapWithConcurrency(
|
|
88759
88934
|
probedCanonicals,
|
|
@@ -88807,7 +88982,7 @@ async function executeDiscovery(opts) {
|
|
|
88807
88982
|
wastedCount: buckets["wasted-surface"],
|
|
88808
88983
|
competitorMap,
|
|
88809
88984
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
88810
|
-
}).where(
|
|
88985
|
+
}).where(eq85(discoverySessions.id, opts.sessionId)).run();
|
|
88811
88986
|
upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
|
|
88812
88987
|
return {
|
|
88813
88988
|
buckets,
|
|
@@ -88847,7 +89022,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
88847
89022
|
status: DiscoverySessionStatuses.failed,
|
|
88848
89023
|
error,
|
|
88849
89024
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
88850
|
-
}).where(
|
|
89025
|
+
}).where(eq85(discoverySessions.id, sessionId)).run();
|
|
88851
89026
|
}
|
|
88852
89027
|
function dedupeStrings(input) {
|
|
88853
89028
|
const seen2 = /* @__PURE__ */ new Set();
|
|
@@ -88865,7 +89040,7 @@ function dedupeStrings(input) {
|
|
|
88865
89040
|
|
|
88866
89041
|
// ../api-routes/src/technical-aeo.ts
|
|
88867
89042
|
import crypto52 from "crypto";
|
|
88868
|
-
import { and as
|
|
89043
|
+
import { and as and72, asc as asc16, count as count4, desc as desc40, eq as eq86, gte as gte22, inArray as inArray35, isNotNull as isNotNull9, isNull as isNull16, lt as lt13, or as or21, sql as sql30 } from "drizzle-orm";
|
|
88869
89044
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
88870
89045
|
var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
|
|
88871
89046
|
...SiteCrawlFetchedStates,
|
|
@@ -89102,8 +89277,8 @@ function parseLinkKind(value) {
|
|
|
89102
89277
|
return allowed.data;
|
|
89103
89278
|
}
|
|
89104
89279
|
function linkKindFilter(linkKind) {
|
|
89105
|
-
if (linkKind === SiteHealthLinkKinds.content) return
|
|
89106
|
-
if (linkKind === SiteHealthLinkKinds.template) return
|
|
89280
|
+
if (linkKind === SiteHealthLinkKinds.content) return eq86(siteCrawlEdges.isTemplate, false);
|
|
89281
|
+
if (linkKind === SiteHealthLinkKinds.template) return eq86(siteCrawlEdges.isTemplate, true);
|
|
89107
89282
|
return void 0;
|
|
89108
89283
|
}
|
|
89109
89284
|
var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
|
|
@@ -89207,20 +89382,20 @@ function changedFields(before, after, fields) {
|
|
|
89207
89382
|
}
|
|
89208
89383
|
async function technicalAeoRoutes(app, opts) {
|
|
89209
89384
|
const visibleCrawlFilters = (projectId) => [
|
|
89210
|
-
|
|
89211
|
-
|
|
89212
|
-
|
|
89213
|
-
|
|
89385
|
+
eq86(siteCrawlSnapshots.projectId, projectId),
|
|
89386
|
+
eq86(runs.projectId, projectId),
|
|
89387
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
89388
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
89214
89389
|
notProbeRun()
|
|
89215
89390
|
];
|
|
89216
89391
|
const availableScanDatesFor = (projectId, requestedDate) => {
|
|
89217
89392
|
if (!requestedDate) return void 0;
|
|
89218
89393
|
const date = sql30`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
|
|
89219
89394
|
const filters = visibleCrawlFilters(projectId);
|
|
89220
|
-
const allDates =
|
|
89221
|
-
const matchingDates =
|
|
89222
|
-
const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
89223
|
-
const countDates = (where) => app.db.select({ value: sql30`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
89395
|
+
const allDates = and72(...filters);
|
|
89396
|
+
const matchingDates = and72(...filters, eq86(sql30`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
|
|
89397
|
+
const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc40(date)).limit(10).all().map((row) => row.date);
|
|
89398
|
+
const countDates = (where) => app.db.select({ value: sql30`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
|
|
89224
89399
|
return {
|
|
89225
89400
|
recentDates: readDates(allDates),
|
|
89226
89401
|
totalDates: countDates(allDates),
|
|
@@ -89237,33 +89412,33 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89237
89412
|
filters.push(gte22(siteCrawlSnapshots.createdAt, requestedDate), lt13(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
|
|
89238
89413
|
}
|
|
89239
89414
|
const selectionFilters = scope.scoredOnly ? [...filters, sql30`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
|
|
89240
|
-
const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
89241
|
-
|
|
89242
|
-
|
|
89243
|
-
|
|
89244
|
-
|
|
89245
|
-
|
|
89415
|
+
const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(and72(...selectionFilters, runId ? eq86(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
|
|
89416
|
+
desc40(sql30`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
|
|
89417
|
+
desc40(siteCrawlSnapshots.complete),
|
|
89418
|
+
desc40(siteCrawlSnapshots.pagesFetched),
|
|
89419
|
+
desc40(siteCrawlSnapshots.createdAt),
|
|
89420
|
+
desc40(siteCrawlSnapshots.runId)
|
|
89246
89421
|
).limit(1).get();
|
|
89247
89422
|
if (!selected) return void 0;
|
|
89248
89423
|
const date = selected.snapshot.createdAt.slice(0, 10);
|
|
89249
89424
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
89250
89425
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
89251
|
-
const sameDate =
|
|
89426
|
+
const sameDate = and72(
|
|
89252
89427
|
...filters,
|
|
89253
89428
|
gte22(siteCrawlSnapshots.createdAt, date),
|
|
89254
89429
|
lt13(siteCrawlSnapshots.createdAt, nextDate.toISOString())
|
|
89255
89430
|
);
|
|
89256
|
-
const sameDateRunCount = app.db.select({ value: count4() }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
89431
|
+
const sameDateRunCount = app.db.select({ value: count4() }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
89257
89432
|
const candidates = app.db.select({
|
|
89258
89433
|
runId: siteCrawlSnapshots.runId,
|
|
89259
89434
|
createdAt: siteCrawlSnapshots.createdAt,
|
|
89260
89435
|
complete: siteCrawlSnapshots.complete,
|
|
89261
89436
|
pages: siteCrawlSnapshots.pagesFetched
|
|
89262
|
-
}).from(siteCrawlSnapshots).innerJoin(runs,
|
|
89263
|
-
|
|
89264
|
-
|
|
89265
|
-
|
|
89266
|
-
|
|
89437
|
+
}).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
89438
|
+
desc40(siteCrawlSnapshots.complete),
|
|
89439
|
+
desc40(siteCrawlSnapshots.pagesFetched),
|
|
89440
|
+
desc40(siteCrawlSnapshots.createdAt),
|
|
89441
|
+
desc40(siteCrawlSnapshots.runId)
|
|
89267
89442
|
).limit(10).all();
|
|
89268
89443
|
const runSelection = {
|
|
89269
89444
|
reason: runId ? "explicit-run" : scope.scoredOnly ? "latest-scored-scan" : requestedDate ? selected.snapshot.complete ? "requested-date-complete" : "requested-date-most-pages" : selected.snapshot.complete ? "latest-date-complete" : "latest-date-most-pages",
|
|
@@ -89277,20 +89452,20 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89277
89452
|
};
|
|
89278
89453
|
const resolveAudit = (projectId, runId, recordedBefore) => {
|
|
89279
89454
|
const filters = [
|
|
89280
|
-
|
|
89281
|
-
|
|
89282
|
-
|
|
89283
|
-
|
|
89455
|
+
eq86(siteAuditSnapshots.projectId, projectId),
|
|
89456
|
+
eq86(runs.projectId, projectId),
|
|
89457
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
89458
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
89284
89459
|
notProbeRun(),
|
|
89285
89460
|
...recordedBefore ? [lt13(siteAuditSnapshots.createdAt, recordedBefore)] : []
|
|
89286
89461
|
];
|
|
89287
89462
|
const complete = sql30`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
|
|
89288
|
-
const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs,
|
|
89289
|
-
|
|
89290
|
-
|
|
89291
|
-
|
|
89292
|
-
|
|
89293
|
-
|
|
89463
|
+
const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(and72(...filters, pinnedRunId ? eq86(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
|
|
89464
|
+
desc40(sql30`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
|
|
89465
|
+
desc40(complete),
|
|
89466
|
+
desc40(siteAuditSnapshots.pagesAudited),
|
|
89467
|
+
desc40(siteAuditSnapshots.createdAt),
|
|
89468
|
+
desc40(siteAuditSnapshots.runId)
|
|
89294
89469
|
).limit(1).get();
|
|
89295
89470
|
const auditOfCrawl = (target) => {
|
|
89296
89471
|
const selected2 = target ? selectAudit(target.snapshot.runId) : void 0;
|
|
@@ -89306,22 +89481,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89306
89481
|
const date = selected.snap.createdAt.slice(0, 10);
|
|
89307
89482
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
89308
89483
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
89309
|
-
const sameDate =
|
|
89484
|
+
const sameDate = and72(
|
|
89310
89485
|
...filters,
|
|
89311
89486
|
gte22(siteAuditSnapshots.createdAt, date),
|
|
89312
89487
|
lt13(siteAuditSnapshots.createdAt, nextDate.toISOString())
|
|
89313
89488
|
);
|
|
89314
|
-
const sameDateRunCount = app.db.select({ value: count4() }).from(siteAuditSnapshots).innerJoin(runs,
|
|
89489
|
+
const sameDateRunCount = app.db.select({ value: count4() }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
89315
89490
|
const candidates = app.db.select({
|
|
89316
89491
|
runId: siteAuditSnapshots.runId,
|
|
89317
89492
|
createdAt: siteAuditSnapshots.createdAt,
|
|
89318
89493
|
complete,
|
|
89319
89494
|
pages: siteAuditSnapshots.pagesAudited
|
|
89320
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
89321
|
-
|
|
89322
|
-
|
|
89323
|
-
|
|
89324
|
-
|
|
89495
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
89496
|
+
desc40(complete),
|
|
89497
|
+
desc40(siteAuditSnapshots.pagesAudited),
|
|
89498
|
+
desc40(siteAuditSnapshots.createdAt),
|
|
89499
|
+
desc40(siteAuditSnapshots.runId)
|
|
89325
89500
|
).limit(10).all().map((candidate) => ({ ...candidate, complete: Boolean(candidate.complete) }));
|
|
89326
89501
|
const runSelection = {
|
|
89327
89502
|
// A crawl exists here only when none stored a scorecard: this score is a fallback.
|
|
@@ -89383,10 +89558,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89383
89558
|
};
|
|
89384
89559
|
const inventorySummaryFor = (scope) => {
|
|
89385
89560
|
if (!scope) return null;
|
|
89386
|
-
const groups = crawlReasonGroups(
|
|
89387
|
-
|
|
89388
|
-
|
|
89389
|
-
|
|
89561
|
+
const groups = crawlReasonGroups(and72(
|
|
89562
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
89563
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
89564
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId)
|
|
89390
89565
|
));
|
|
89391
89566
|
const total = groups.reduce((sum, group) => sum + group.pages, 0);
|
|
89392
89567
|
const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
|
|
@@ -89406,34 +89581,34 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89406
89581
|
const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
|
|
89407
89582
|
const pageInScope = (scope, selector) => {
|
|
89408
89583
|
if (!selector.nodeKey && !selector.url) return null;
|
|
89409
|
-
return app.db.select().from(siteCrawlPages).where(
|
|
89410
|
-
|
|
89411
|
-
|
|
89412
|
-
|
|
89413
|
-
selector.nodeKey ?
|
|
89584
|
+
return app.db.select().from(siteCrawlPages).where(and72(
|
|
89585
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
89586
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
89587
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
89588
|
+
selector.nodeKey ? eq86(siteCrawlPages.nodeKey, selector.nodeKey) : eq86(siteCrawlPages.url, selector.url)
|
|
89414
89589
|
)).orderBy(asc16(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
89415
89590
|
};
|
|
89416
|
-
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(
|
|
89417
|
-
|
|
89418
|
-
|
|
89419
|
-
|
|
89420
|
-
|
|
89591
|
+
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and72(
|
|
89592
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
89593
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
89594
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
89595
|
+
eq86(siteCrawlPages.depth, 0)
|
|
89421
89596
|
)).orderBy(asc16(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
89422
|
-
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(
|
|
89423
|
-
|
|
89424
|
-
|
|
89425
|
-
|
|
89426
|
-
|
|
89597
|
+
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and72(
|
|
89598
|
+
eq86(runs.id, runId),
|
|
89599
|
+
eq86(runs.projectId, projectId),
|
|
89600
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
89601
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
89427
89602
|
notProbeRun()
|
|
89428
89603
|
)).limit(1).get());
|
|
89429
89604
|
const assertKnownAuditRun = (projectId, runId) => {
|
|
89430
89605
|
if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
|
|
89431
89606
|
};
|
|
89432
|
-
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs,
|
|
89433
|
-
|
|
89434
|
-
|
|
89435
|
-
|
|
89436
|
-
|
|
89607
|
+
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(and72(
|
|
89608
|
+
eq86(siteAuditSnapshots.projectId, projectId),
|
|
89609
|
+
eq86(runs.projectId, projectId),
|
|
89610
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
89611
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
89437
89612
|
notProbeRun()
|
|
89438
89613
|
)).limit(1).get());
|
|
89439
89614
|
const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
|
|
@@ -89534,14 +89709,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89534
89709
|
};
|
|
89535
89710
|
}
|
|
89536
89711
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
89537
|
-
const conds = [
|
|
89538
|
-
if (statusFilter) conds.push(
|
|
89539
|
-
const where =
|
|
89712
|
+
const conds = [eq86(siteAuditPages.projectId, project.id), eq86(siteAuditPages.runId, latest.snap.runId)];
|
|
89713
|
+
if (statusFilter) conds.push(eq86(siteAuditPages.status, statusFilter));
|
|
89714
|
+
const where = and72(...conds);
|
|
89540
89715
|
const totalRow = app.db.select({ value: count4() }).from(siteAuditPages).where(where).get();
|
|
89541
89716
|
const total = totalRow?.value ?? 0;
|
|
89542
89717
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
89543
89718
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
89544
|
-
const orderBy = request.query.sort === "score-desc" ?
|
|
89719
|
+
const orderBy = request.query.sort === "score-desc" ? desc40(siteAuditPages.overallScore) : request.query.sort === "url" ? asc16(siteAuditPages.url) : asc16(siteAuditPages.overallScore);
|
|
89545
89720
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
89546
89721
|
const pages = rows.map((row) => ({
|
|
89547
89722
|
url: row.url,
|
|
@@ -89560,13 +89735,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89560
89735
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
89561
89736
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
89562
89737
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
89563
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
89564
|
-
|
|
89565
|
-
|
|
89566
|
-
|
|
89567
|
-
|
|
89738
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(and72(
|
|
89739
|
+
eq86(siteAuditSnapshots.projectId, project.id),
|
|
89740
|
+
eq86(runs.projectId, project.id),
|
|
89741
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
89742
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
89568
89743
|
notProbeRun()
|
|
89569
|
-
)).orderBy(
|
|
89744
|
+
)).orderBy(desc40(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
89570
89745
|
return { project: project.name, points: rows.reverse() };
|
|
89571
89746
|
});
|
|
89572
89747
|
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
@@ -89666,10 +89841,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89666
89841
|
{ projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
|
|
89667
89842
|
snapshot.rootUrl
|
|
89668
89843
|
)?.nodeKey ?? null;
|
|
89669
|
-
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(
|
|
89670
|
-
|
|
89671
|
-
|
|
89672
|
-
|
|
89844
|
+
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and72(
|
|
89845
|
+
eq86(siteCrawlGraphLayouts.projectId, project.id),
|
|
89846
|
+
eq86(siteCrawlGraphLayouts.runId, snapshot.runId),
|
|
89847
|
+
eq86(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
|
|
89673
89848
|
)).limit(1).get();
|
|
89674
89849
|
if (!persistedLayout) {
|
|
89675
89850
|
return {
|
|
@@ -89722,9 +89897,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89722
89897
|
const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
|
|
89723
89898
|
const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
|
|
89724
89899
|
const graphScope = [
|
|
89725
|
-
|
|
89726
|
-
|
|
89727
|
-
|
|
89900
|
+
eq86(siteCrawlGraphNodes.projectId, project.id),
|
|
89901
|
+
eq86(siteCrawlGraphNodes.runId, snapshot.runId),
|
|
89902
|
+
eq86(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
|
|
89728
89903
|
];
|
|
89729
89904
|
const nodeRows = app.db.select({
|
|
89730
89905
|
nodeKey: siteCrawlPages.nodeKey,
|
|
@@ -89743,18 +89918,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89743
89918
|
linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
|
|
89744
89919
|
x: siteCrawlGraphNodes.x,
|
|
89745
89920
|
y: siteCrawlGraphNodes.y
|
|
89746
|
-
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages,
|
|
89747
|
-
|
|
89748
|
-
|
|
89749
|
-
|
|
89750
|
-
|
|
89751
|
-
)).where(
|
|
89921
|
+
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and72(
|
|
89922
|
+
eq86(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
|
|
89923
|
+
eq86(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
|
|
89924
|
+
eq86(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
|
|
89925
|
+
eq86(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
|
|
89926
|
+
)).where(and72(...graphScope, lt13(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc16(siteCrawlGraphNodes.sampleRank)).all();
|
|
89752
89927
|
const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
|
|
89753
89928
|
...row,
|
|
89754
89929
|
healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
|
|
89755
89930
|
healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
|
|
89756
89931
|
}));
|
|
89757
|
-
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 :
|
|
89932
|
+
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq86(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
|
|
89758
89933
|
const edges = app.db.select({
|
|
89759
89934
|
edgeKey: siteCrawlGraphEdges.edgeKey,
|
|
89760
89935
|
sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
|
|
@@ -89762,22 +89937,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89762
89937
|
followable: siteCrawlGraphEdges.followable,
|
|
89763
89938
|
occurrences: siteCrawlGraphEdges.occurrences,
|
|
89764
89939
|
isTemplate: siteCrawlGraphEdges.isTemplate
|
|
89765
|
-
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode,
|
|
89766
|
-
|
|
89767
|
-
|
|
89768
|
-
|
|
89769
|
-
|
|
89940
|
+
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and72(
|
|
89941
|
+
eq86(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
|
|
89942
|
+
eq86(graphSourceNode.runId, siteCrawlGraphEdges.runId),
|
|
89943
|
+
eq86(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
89944
|
+
eq86(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
|
|
89770
89945
|
lt13(graphSourceNode.sampleRank, maxNodes)
|
|
89771
|
-
)).innerJoin(graphTargetNode,
|
|
89772
|
-
|
|
89773
|
-
|
|
89774
|
-
|
|
89775
|
-
|
|
89946
|
+
)).innerJoin(graphTargetNode, and72(
|
|
89947
|
+
eq86(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
|
|
89948
|
+
eq86(graphTargetNode.runId, siteCrawlGraphEdges.runId),
|
|
89949
|
+
eq86(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
89950
|
+
eq86(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
|
|
89776
89951
|
lt13(graphTargetNode.sampleRank, maxNodes)
|
|
89777
|
-
)).where(
|
|
89778
|
-
|
|
89779
|
-
|
|
89780
|
-
|
|
89952
|
+
)).where(and72(
|
|
89953
|
+
eq86(siteCrawlGraphEdges.projectId, project.id),
|
|
89954
|
+
eq86(siteCrawlGraphEdges.runId, snapshot.runId),
|
|
89955
|
+
eq86(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
|
|
89781
89956
|
lt13(siteCrawlGraphEdges.sampleRank, maxEdges),
|
|
89782
89957
|
graphLinkKindFilter
|
|
89783
89958
|
)).orderBy(asc16(siteCrawlGraphEdges.sampleRank)).all();
|
|
@@ -89872,15 +90047,15 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89872
90047
|
let frontier = [focus.nodeKey];
|
|
89873
90048
|
let queryTruncated = false;
|
|
89874
90049
|
for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
|
|
89875
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
89876
|
-
|
|
89877
|
-
|
|
89878
|
-
|
|
89879
|
-
|
|
89880
|
-
|
|
89881
|
-
|
|
89882
|
-
|
|
89883
|
-
|
|
90050
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and72(
|
|
90051
|
+
eq86(siteCrawlEdges.projectId, scope.projectId),
|
|
90052
|
+
eq86(siteCrawlEdges.runId, scope.runId),
|
|
90053
|
+
eq86(siteCrawlEdges.attemptId, scope.attemptId),
|
|
90054
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90055
|
+
isNotNull9(siteCrawlEdges.targetNodeKey),
|
|
90056
|
+
or21(
|
|
90057
|
+
inArray35(siteCrawlEdges.sourceNodeKey, frontier),
|
|
90058
|
+
inArray35(siteCrawlEdges.targetNodeKey, frontier)
|
|
89884
90059
|
)
|
|
89885
90060
|
)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
|
|
89886
90061
|
if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
|
|
@@ -89913,22 +90088,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89913
90088
|
frontier = [...next].sort();
|
|
89914
90089
|
}
|
|
89915
90090
|
const nodeKeys = [...distances.keys()];
|
|
89916
|
-
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
89917
|
-
|
|
89918
|
-
|
|
89919
|
-
|
|
89920
|
-
|
|
90091
|
+
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and72(
|
|
90092
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
90093
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
90094
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
90095
|
+
inArray35(siteCrawlPages.nodeKey, nodeKeys)
|
|
89921
90096
|
)).all();
|
|
89922
90097
|
const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
|
|
89923
90098
|
const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
|
|
89924
|
-
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(
|
|
89925
|
-
|
|
89926
|
-
|
|
89927
|
-
|
|
89928
|
-
|
|
89929
|
-
|
|
89930
|
-
|
|
89931
|
-
|
|
90099
|
+
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and72(
|
|
90100
|
+
eq86(siteCrawlEdges.projectId, scope.projectId),
|
|
90101
|
+
eq86(siteCrawlEdges.runId, scope.runId),
|
|
90102
|
+
eq86(siteCrawlEdges.attemptId, scope.attemptId),
|
|
90103
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90104
|
+
isNotNull9(siteCrawlEdges.targetNodeKey),
|
|
90105
|
+
inArray35(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
|
|
90106
|
+
inArray35(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
|
|
89932
90107
|
)).limit(1).get();
|
|
89933
90108
|
if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
|
|
89934
90109
|
omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
|
|
@@ -90021,23 +90196,23 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90021
90196
|
let found = fromPage.nodeKey === toPage.nodeKey;
|
|
90022
90197
|
let truncated = false;
|
|
90023
90198
|
for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
|
|
90024
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
90025
|
-
|
|
90026
|
-
|
|
90027
|
-
|
|
90028
|
-
|
|
90029
|
-
|
|
90030
|
-
|
|
90031
|
-
|
|
90032
|
-
|
|
90199
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and72(
|
|
90200
|
+
eq86(siteCrawlEdges.projectId, scope.projectId),
|
|
90201
|
+
eq86(siteCrawlEdges.runId, scope.runId),
|
|
90202
|
+
eq86(siteCrawlEdges.attemptId, scope.attemptId),
|
|
90203
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90204
|
+
eq86(siteCrawlEdges.followable, true),
|
|
90205
|
+
eq86(siteCrawlEdges.relation, "anchor"),
|
|
90206
|
+
isNotNull9(siteCrawlEdges.targetNodeKey),
|
|
90207
|
+
inArray35(siteCrawlEdges.sourceNodeKey, frontier)
|
|
90033
90208
|
)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
|
|
90034
90209
|
if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
|
|
90035
90210
|
const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
|
|
90036
|
-
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(
|
|
90037
|
-
|
|
90038
|
-
|
|
90039
|
-
|
|
90040
|
-
|
|
90211
|
+
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and72(
|
|
90212
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
90213
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
90214
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
90215
|
+
inArray35(siteCrawlPages.nodeKey, candidateTargetKeys)
|
|
90041
90216
|
)).all().map((row) => row.nodeKey));
|
|
90042
90217
|
const next = /* @__PURE__ */ new Set();
|
|
90043
90218
|
for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
|
|
@@ -90078,11 +90253,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90078
90253
|
pathEdges.unshift(step.edge);
|
|
90079
90254
|
pathKeys.unshift(step.nodeKey);
|
|
90080
90255
|
}
|
|
90081
|
-
const pathRows = app.db.select().from(siteCrawlPages).where(
|
|
90082
|
-
|
|
90083
|
-
|
|
90084
|
-
|
|
90085
|
-
|
|
90256
|
+
const pathRows = app.db.select().from(siteCrawlPages).where(and72(
|
|
90257
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
90258
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
90259
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
90260
|
+
inArray35(siteCrawlPages.nodeKey, pathKeys)
|
|
90086
90261
|
)).all();
|
|
90087
90262
|
const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
|
|
90088
90263
|
const pathDetection = templateDetectionOf(snapshot.templateDetection);
|
|
@@ -90110,25 +90285,25 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90110
90285
|
throw validationError("change must be all, added, removed, or changed");
|
|
90111
90286
|
}
|
|
90112
90287
|
const completeSnapshotFilters = [
|
|
90113
|
-
|
|
90114
|
-
|
|
90115
|
-
|
|
90116
|
-
|
|
90117
|
-
|
|
90288
|
+
eq86(siteCrawlSnapshots.projectId, project.id),
|
|
90289
|
+
eq86(runs.projectId, project.id),
|
|
90290
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
90291
|
+
eq86(runs.status, RunStatuses.completed),
|
|
90292
|
+
eq86(siteCrawlSnapshots.complete, true),
|
|
90118
90293
|
notProbeRun()
|
|
90119
90294
|
];
|
|
90120
|
-
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
90295
|
+
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(and72(...completeSnapshotFilters)).orderBy(desc40(siteCrawlSnapshots.createdAt), desc40(siteCrawlSnapshots.runId)).limit(1).get();
|
|
90121
90296
|
if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
|
|
90122
|
-
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
90297
|
+
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(and72(
|
|
90123
90298
|
...completeSnapshotFilters,
|
|
90124
|
-
|
|
90299
|
+
or21(
|
|
90125
90300
|
lt13(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
90126
|
-
|
|
90127
|
-
|
|
90301
|
+
and72(
|
|
90302
|
+
eq86(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
90128
90303
|
lt13(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
|
|
90129
90304
|
)
|
|
90130
90305
|
)
|
|
90131
|
-
)).orderBy(
|
|
90306
|
+
)).orderBy(desc40(siteCrawlSnapshots.createdAt), desc40(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
|
|
90132
90307
|
if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
|
|
90133
90308
|
if (!afterTarget) {
|
|
90134
90309
|
const latestCrawl = resolveCrawl(project.id);
|
|
@@ -90369,18 +90544,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90369
90544
|
const visibleKeys = keyRows.slice(0, limit);
|
|
90370
90545
|
const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
|
|
90371
90546
|
const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
|
|
90372
|
-
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
90373
|
-
|
|
90374
|
-
|
|
90375
|
-
|
|
90376
|
-
|
|
90547
|
+
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and72(
|
|
90548
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
90549
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
90550
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
90551
|
+
inArray35(siteCrawlPages.nodeKey, pageKeys)
|
|
90377
90552
|
)).all();
|
|
90378
|
-
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(
|
|
90379
|
-
|
|
90380
|
-
|
|
90381
|
-
|
|
90382
|
-
|
|
90383
|
-
|
|
90553
|
+
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and72(
|
|
90554
|
+
eq86(siteCrawlEdges.projectId, scope.projectId),
|
|
90555
|
+
eq86(siteCrawlEdges.runId, scope.runId),
|
|
90556
|
+
eq86(siteCrawlEdges.attemptId, scope.attemptId),
|
|
90557
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90558
|
+
inArray35(siteCrawlEdges.edgeKey, linkKeys)
|
|
90384
90559
|
)).all();
|
|
90385
90560
|
const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
90386
90561
|
const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
@@ -90533,16 +90708,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90533
90708
|
}
|
|
90534
90709
|
const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
|
|
90535
90710
|
const filters = [
|
|
90536
|
-
|
|
90537
|
-
|
|
90538
|
-
|
|
90711
|
+
eq86(siteCrawlPages.projectId, project.id),
|
|
90712
|
+
eq86(siteCrawlPages.runId, snapshot.runId),
|
|
90713
|
+
eq86(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
90539
90714
|
];
|
|
90540
|
-
if (request.query.nodeKey) filters.push(
|
|
90715
|
+
if (request.query.nodeKey) filters.push(eq86(siteCrawlPages.nodeKey, request.query.nodeKey));
|
|
90541
90716
|
const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
|
|
90542
|
-
if (inventoryEligible != null) filters.push(
|
|
90543
|
-
if (request.query.fetchState) filters.push(
|
|
90544
|
-
if (request.query.indexabilityState) filters.push(
|
|
90545
|
-
if (request.query.auditState) filters.push(
|
|
90717
|
+
if (inventoryEligible != null) filters.push(eq86(siteCrawlPages.inventoryEligible, inventoryEligible));
|
|
90718
|
+
if (request.query.fetchState) filters.push(eq86(siteCrawlPages.fetchState, request.query.fetchState));
|
|
90719
|
+
if (request.query.indexabilityState) filters.push(eq86(siteCrawlPages.indexabilityState, request.query.indexabilityState));
|
|
90720
|
+
if (request.query.auditState) filters.push(eq86(siteCrawlPages.auditState, request.query.auditState));
|
|
90546
90721
|
const healthState = parseSiteHealthState(request.query.healthState);
|
|
90547
90722
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
90548
90723
|
const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
|
|
@@ -90560,17 +90735,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90560
90735
|
sort
|
|
90561
90736
|
})).digest("base64url");
|
|
90562
90737
|
const offset = decodeCrawlPageCursor(request.query.cursor, selectionKey);
|
|
90563
|
-
const orderBy = sort === "score-desc" ? [
|
|
90738
|
+
const orderBy = sort === "score-desc" ? [desc40(siteCrawlPages.auditScore), asc16(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc16(siteCrawlPages.auditScore), asc16(siteCrawlPages.nodeKey)] : sort === "path" ? [asc16(siteCrawlPages.path), asc16(siteCrawlPages.nodeKey)] : [asc16(siteCrawlPages.url), asc16(siteCrawlPages.nodeKey)];
|
|
90564
90739
|
let healthStateFilter = null;
|
|
90565
90740
|
if (healthState) {
|
|
90566
|
-
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(
|
|
90567
|
-
|
|
90568
|
-
|
|
90569
|
-
|
|
90741
|
+
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and72(
|
|
90742
|
+
eq86(siteCrawlPages.projectId, project.id),
|
|
90743
|
+
eq86(siteCrawlPages.runId, snapshot.runId),
|
|
90744
|
+
eq86(siteCrawlPages.attemptId, snapshot.attemptId),
|
|
90570
90745
|
isNull16(siteCrawlPages.healthState)
|
|
90571
90746
|
)).limit(1).get();
|
|
90572
90747
|
healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
|
|
90573
|
-
if (healthStateFilter === "applied") filters.push(
|
|
90748
|
+
if (healthStateFilter === "applied") filters.push(eq86(siteCrawlPages.healthState, healthState));
|
|
90574
90749
|
}
|
|
90575
90750
|
if (healthStateFilter === "unavailable-legacy-scan") {
|
|
90576
90751
|
return {
|
|
@@ -90587,7 +90762,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90587
90762
|
pages: []
|
|
90588
90763
|
};
|
|
90589
90764
|
}
|
|
90590
|
-
const where =
|
|
90765
|
+
const where = and72(...filters);
|
|
90591
90766
|
const total = app.db.select({ value: count4() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
|
|
90592
90767
|
const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
|
|
90593
90768
|
const nextOffset = offset + rows.length;
|
|
@@ -90622,10 +90797,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90622
90797
|
url: siteCrawlPages.url,
|
|
90623
90798
|
inventoryEligible: siteCrawlPages.inventoryEligible,
|
|
90624
90799
|
fetchState: siteCrawlPages.fetchState
|
|
90625
|
-
}).from(siteCrawlPages).where(
|
|
90626
|
-
|
|
90627
|
-
|
|
90628
|
-
|
|
90800
|
+
}).from(siteCrawlPages).where(and72(
|
|
90801
|
+
eq86(siteCrawlPages.projectId, project.id),
|
|
90802
|
+
eq86(siteCrawlPages.runId, snapshot.runId),
|
|
90803
|
+
eq86(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
90629
90804
|
)).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
|
|
90630
90805
|
if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
|
|
90631
90806
|
throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
|
|
@@ -90707,17 +90882,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90707
90882
|
};
|
|
90708
90883
|
}
|
|
90709
90884
|
const filters = [
|
|
90710
|
-
|
|
90711
|
-
|
|
90712
|
-
|
|
90713
|
-
|
|
90885
|
+
eq86(siteCrawlEdges.projectId, project.id),
|
|
90886
|
+
eq86(siteCrawlEdges.runId, snapshot.runId),
|
|
90887
|
+
eq86(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
90888
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90714
90889
|
linkKindFilter(linkKind)
|
|
90715
90890
|
];
|
|
90716
|
-
if (request.query.sourceUrl) filters.push(
|
|
90717
|
-
if (request.query.targetUrl) filters.push(
|
|
90891
|
+
if (request.query.sourceUrl) filters.push(eq86(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
|
|
90892
|
+
if (request.query.targetUrl) filters.push(eq86(siteCrawlEdges.targetUrl, request.query.targetUrl));
|
|
90718
90893
|
const followable = parseBoolean2(request.query.followable);
|
|
90719
|
-
if (followable != null) filters.push(
|
|
90720
|
-
const where =
|
|
90894
|
+
if (followable != null) filters.push(eq86(siteCrawlEdges.followable, followable));
|
|
90895
|
+
const where = and72(...filters);
|
|
90721
90896
|
const total = app.db.select({ value: count4() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
|
|
90722
90897
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
90723
90898
|
const offset = decodeCursor(request.query.cursor);
|
|
@@ -90778,17 +90953,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90778
90953
|
};
|
|
90779
90954
|
}
|
|
90780
90955
|
const scope = [
|
|
90781
|
-
|
|
90782
|
-
|
|
90783
|
-
|
|
90784
|
-
|
|
90956
|
+
eq86(siteCrawlEdges.projectId, project.id),
|
|
90957
|
+
eq86(siteCrawlEdges.runId, snapshot.runId),
|
|
90958
|
+
eq86(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
90959
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90785
90960
|
linkKindFilter(linkKind)
|
|
90786
90961
|
];
|
|
90787
|
-
const inboundMatch = request.query.nodeKey && request.query.url ?
|
|
90788
|
-
const outboundMatch = request.query.nodeKey && request.query.url ?
|
|
90962
|
+
const inboundMatch = request.query.nodeKey && request.query.url ? or21(eq86(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq86(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq86(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq86(siteCrawlEdges.targetUrl, request.query.url);
|
|
90963
|
+
const outboundMatch = request.query.nodeKey && request.query.url ? or21(eq86(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq86(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq86(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq86(siteCrawlEdges.sourceUrl, request.query.url);
|
|
90789
90964
|
const limit = parseBoundedLimit(request.query.limit, 50, 100);
|
|
90790
|
-
const inboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
90791
|
-
const outboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
90965
|
+
const inboundRows = app.db.select().from(siteCrawlEdges).where(and72(...scope, inboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
90966
|
+
const outboundRows = app.db.select().from(siteCrawlEdges).where(and72(...scope, outboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
90792
90967
|
return {
|
|
90793
90968
|
project: project.name,
|
|
90794
90969
|
hasCrawlData: true,
|
|
@@ -90822,11 +90997,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90822
90997
|
if (!snapshot.attemptId) {
|
|
90823
90998
|
return { project: project.name, runId: snapshot.runId, state: "partial", checkDeadLinks: true, checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound, unverified: snapshot.deadLinksUnverified, total: 0, nextCursor: null, deadLinks: [], runSelection: target.runSelection };
|
|
90824
90999
|
}
|
|
90825
|
-
const where =
|
|
90826
|
-
|
|
90827
|
-
|
|
90828
|
-
|
|
90829
|
-
|
|
91000
|
+
const where = and72(
|
|
91001
|
+
eq86(siteCrawlFindings.projectId, project.id),
|
|
91002
|
+
eq86(siteCrawlFindings.runId, snapshot.runId),
|
|
91003
|
+
eq86(siteCrawlFindings.attemptId, snapshot.attemptId),
|
|
91004
|
+
eq86(siteCrawlFindings.findingType, "dead-link"),
|
|
90830
91005
|
// Belt and braces on the client-facing surface: a row without a status
|
|
90831
91006
|
// code is not evidence of a broken link, and this response is where such
|
|
90832
91007
|
// a row would be read as one. Migration 140 removed every stored row that
|
|
@@ -90836,7 +91011,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90836
91011
|
// a fabricated broken link shown to a client is not worth the saved
|
|
90837
91012
|
// predicate. Applied to `total` as well as the rows, or the count would
|
|
90838
91013
|
// promise a page the list cannot fill.
|
|
90839
|
-
|
|
91014
|
+
isNotNull9(sql30`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
|
|
90840
91015
|
);
|
|
90841
91016
|
const total = app.db.select({ value: count4() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
|
|
90842
91017
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
@@ -90876,19 +91051,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90876
91051
|
createdAt: runs.createdAt,
|
|
90877
91052
|
startedAt: runs.startedAt,
|
|
90878
91053
|
finishedAt: runs.finishedAt
|
|
90879
|
-
}).from(runs).where(
|
|
90880
|
-
|
|
90881
|
-
|
|
91054
|
+
}).from(runs).where(and72(
|
|
91055
|
+
eq86(runs.projectId, project.id),
|
|
91056
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
90882
91057
|
notProbeRun()
|
|
90883
|
-
)).orderBy(
|
|
91058
|
+
)).orderBy(desc40(runs.createdAt), desc40(runs.id)).limit(limit).all();
|
|
90884
91059
|
const runIds = rows.map((row) => row.runId);
|
|
90885
|
-
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
90886
|
-
|
|
90887
|
-
|
|
90888
|
-
|
|
90889
|
-
|
|
91060
|
+
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(and72(
|
|
91061
|
+
eq86(siteCrawlSnapshots.projectId, project.id),
|
|
91062
|
+
eq86(runs.projectId, project.id),
|
|
91063
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
91064
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
90890
91065
|
notProbeRun(),
|
|
90891
|
-
|
|
91066
|
+
inArray35(siteCrawlSnapshots.runId, runIds)
|
|
90892
91067
|
)).all().map((row) => row.runId));
|
|
90893
91068
|
return {
|
|
90894
91069
|
project: project.name,
|
|
@@ -90902,21 +91077,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90902
91077
|
});
|
|
90903
91078
|
app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
|
|
90904
91079
|
const project = resolveProject(app.db, request.params.name);
|
|
90905
|
-
const run = app.db.select().from(runs).where(
|
|
90906
|
-
|
|
90907
|
-
|
|
90908
|
-
|
|
91080
|
+
const run = app.db.select().from(runs).where(and72(
|
|
91081
|
+
eq86(runs.id, request.params.runId),
|
|
91082
|
+
eq86(runs.projectId, project.id),
|
|
91083
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
90909
91084
|
notProbeRun()
|
|
90910
91085
|
)).get();
|
|
90911
91086
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
90912
|
-
const attempt = app.db.select().from(siteCrawlAttempts).where(
|
|
90913
|
-
|
|
90914
|
-
|
|
90915
|
-
)).orderBy(
|
|
90916
|
-
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(
|
|
90917
|
-
|
|
90918
|
-
|
|
90919
|
-
|
|
91087
|
+
const attempt = app.db.select().from(siteCrawlAttempts).where(and72(
|
|
91088
|
+
eq86(siteCrawlAttempts.projectId, project.id),
|
|
91089
|
+
eq86(siteCrawlAttempts.runId, run.id)
|
|
91090
|
+
)).orderBy(desc40(siteCrawlAttempts.attemptNumber), desc40(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
91091
|
+
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and72(
|
|
91092
|
+
eq86(siteCrawlGraphLayouts.projectId, project.id),
|
|
91093
|
+
eq86(siteCrawlGraphLayouts.runId, run.id),
|
|
91094
|
+
eq86(siteCrawlGraphLayouts.attemptId, attempt.id)
|
|
90920
91095
|
)).get() : void 0;
|
|
90921
91096
|
const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
|
|
90922
91097
|
return {
|
|
@@ -90947,19 +91122,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90947
91122
|
};
|
|
90948
91123
|
});
|
|
90949
91124
|
app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
|
|
90950
|
-
const project = tx.select().from(projects).where(
|
|
91125
|
+
const project = tx.select().from(projects).where(eq86(projects.name, request.params.name)).get();
|
|
90951
91126
|
if (!project) throw notFound("Project", request.params.name);
|
|
90952
|
-
const run = tx.select().from(runs).where(
|
|
90953
|
-
|
|
90954
|
-
|
|
90955
|
-
|
|
91127
|
+
const run = tx.select().from(runs).where(and72(
|
|
91128
|
+
eq86(runs.id, request.params.runId),
|
|
91129
|
+
eq86(runs.projectId, project.id),
|
|
91130
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
90956
91131
|
notProbeRun()
|
|
90957
91132
|
)).get();
|
|
90958
91133
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
90959
|
-
const attempt = tx.select().from(siteCrawlAttempts).where(
|
|
90960
|
-
|
|
90961
|
-
|
|
90962
|
-
)).orderBy(
|
|
91134
|
+
const attempt = tx.select().from(siteCrawlAttempts).where(and72(
|
|
91135
|
+
eq86(siteCrawlAttempts.projectId, project.id),
|
|
91136
|
+
eq86(siteCrawlAttempts.runId, run.id)
|
|
91137
|
+
)).orderBy(desc40(siteCrawlAttempts.attemptNumber), desc40(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
90963
91138
|
const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
|
|
90964
91139
|
const base = {
|
|
90965
91140
|
project: project.name,
|
|
@@ -90972,11 +91147,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90972
91147
|
if (!attempt) {
|
|
90973
91148
|
return { ...base, pagesAudited: 0, examples: [] };
|
|
90974
91149
|
}
|
|
90975
|
-
const auditedWhere =
|
|
90976
|
-
|
|
90977
|
-
|
|
90978
|
-
|
|
90979
|
-
|
|
91150
|
+
const auditedWhere = and72(
|
|
91151
|
+
eq86(siteCrawlPages.projectId, project.id),
|
|
91152
|
+
eq86(siteCrawlPages.runId, run.id),
|
|
91153
|
+
eq86(siteCrawlPages.attemptId, attempt.id),
|
|
91154
|
+
eq86(siteCrawlPages.auditState, "success")
|
|
90980
91155
|
);
|
|
90981
91156
|
const pagesAudited = tx.select({ value: count4() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
|
|
90982
91157
|
if (state !== "collecting") {
|
|
@@ -90987,9 +91162,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90987
91162
|
url: siteCrawlPages.url,
|
|
90988
91163
|
auditScore: siteCrawlPages.auditScore,
|
|
90989
91164
|
auditFields: siteCrawlPages.auditFields
|
|
90990
|
-
}).from(siteCrawlPages).where(
|
|
91165
|
+
}).from(siteCrawlPages).where(and72(
|
|
90991
91166
|
auditedWhere,
|
|
90992
|
-
|
|
91167
|
+
isNotNull9(siteCrawlPages.auditScore),
|
|
90993
91168
|
lt13(siteCrawlPages.auditScore, 70)
|
|
90994
91169
|
)).orderBy(asc16(siteCrawlPages.auditScore), asc16(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
|
|
90995
91170
|
const examples = candidates.flatMap((candidate) => {
|
|
@@ -91023,13 +91198,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91023
91198
|
status: runs.status,
|
|
91024
91199
|
identityKey: siteCrawlRunRequests.identityKey,
|
|
91025
91200
|
effectiveOptions: siteCrawlRunRequests.effectiveOptions
|
|
91026
|
-
}).from(runs).leftJoin(siteCrawlRunRequests,
|
|
91027
|
-
|
|
91028
|
-
|
|
91029
|
-
)).where(
|
|
91030
|
-
|
|
91031
|
-
|
|
91032
|
-
|
|
91201
|
+
}).from(runs).leftJoin(siteCrawlRunRequests, and72(
|
|
91202
|
+
eq86(siteCrawlRunRequests.projectId, runs.projectId),
|
|
91203
|
+
eq86(siteCrawlRunRequests.runId, runs.id)
|
|
91204
|
+
)).where(and72(
|
|
91205
|
+
eq86(runs.projectId, project.id),
|
|
91206
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
91207
|
+
inArray35(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
91033
91208
|
)).get();
|
|
91034
91209
|
if (existing) {
|
|
91035
91210
|
if (existing.identityKey === identityKey) {
|
|
@@ -91083,7 +91258,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91083
91258
|
// ../api-routes/src/research.ts
|
|
91084
91259
|
import crypto53 from "crypto";
|
|
91085
91260
|
import { z as z5 } from "zod";
|
|
91086
|
-
import { and as
|
|
91261
|
+
import { and as and73, count as count5, desc as desc41, eq as eq87, gte as gte23, lt as lt14, or as or22, sql as sql31 } from "drizzle-orm";
|
|
91087
91262
|
var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
|
|
91088
91263
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
91089
91264
|
async function researchRoutes(app, opts) {
|
|
@@ -91101,7 +91276,7 @@ async function researchRoutes(app, opts) {
|
|
|
91101
91276
|
const input = parsed.data;
|
|
91102
91277
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
91103
91278
|
const requestHash2 = directResearchRequestHash(input);
|
|
91104
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
91279
|
+
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and73(eq87(researchRuns.projectId, project.id), eq87(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
|
|
91105
91280
|
if (existingReceipt) {
|
|
91106
91281
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
91107
91282
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -91133,7 +91308,7 @@ async function researchRoutes(app, opts) {
|
|
|
91133
91308
|
const initiatedBy = researchPrincipal(request);
|
|
91134
91309
|
const decision = app.db.transaction((tx) => {
|
|
91135
91310
|
if (input.idempotencyKey) {
|
|
91136
|
-
const existing = tx.select().from(researchRuns).where(
|
|
91311
|
+
const existing = tx.select().from(researchRuns).where(and73(eq87(researchRuns.projectId, project.id), eq87(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
91137
91312
|
if (existing) {
|
|
91138
91313
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
91139
91314
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -91141,8 +91316,8 @@ async function researchRoutes(app, opts) {
|
|
|
91141
91316
|
}
|
|
91142
91317
|
if (initiatedBy?.limited) {
|
|
91143
91318
|
const { start, end, date } = utcDayBounds(now);
|
|
91144
|
-
const used = tx.select({ value: count5() }).from(researchRuns).where(
|
|
91145
|
-
|
|
91319
|
+
const used = tx.select({ value: count5() }).from(researchRuns).where(and73(
|
|
91320
|
+
eq87(researchRuns.projectId, project.id),
|
|
91146
91321
|
gte23(researchRuns.createdAt, start),
|
|
91147
91322
|
lt14(researchRuns.createdAt, end),
|
|
91148
91323
|
sql31`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -91173,15 +91348,15 @@ async function researchRoutes(app, opts) {
|
|
|
91173
91348
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
91174
91349
|
const requestHash2 = researchBatchRequestHash(input);
|
|
91175
91350
|
const receiptKeys = input.runs.map((_run, index2) => batchChildReceiptKey(input.idempotencyKey, index2));
|
|
91176
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
91177
|
-
|
|
91178
|
-
|
|
91351
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and73(
|
|
91352
|
+
eq87(researchRuns.projectId, project.id),
|
|
91353
|
+
eq87(researchRuns.idempotencyKey, receiptKeys[0])
|
|
91179
91354
|
)).get();
|
|
91180
91355
|
if (firstReceipt) {
|
|
91181
91356
|
if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
91182
|
-
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(
|
|
91183
|
-
|
|
91184
|
-
|
|
91357
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and73(
|
|
91358
|
+
eq87(researchRuns.projectId, project.id),
|
|
91359
|
+
eq87(researchRuns.idempotencyKey, key)
|
|
91185
91360
|
)).get());
|
|
91186
91361
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
91187
91362
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -91196,8 +91371,8 @@ async function researchRoutes(app, opts) {
|
|
|
91196
91371
|
const decision = app.db.transaction((tx) => {
|
|
91197
91372
|
if (initiatedBy?.limited) {
|
|
91198
91373
|
const { start, end, date } = utcDayBounds(now);
|
|
91199
|
-
const used = tx.select({ value: count5() }).from(researchRuns).where(
|
|
91200
|
-
|
|
91374
|
+
const used = tx.select({ value: count5() }).from(researchRuns).where(and73(
|
|
91375
|
+
eq87(researchRuns.projectId, project.id),
|
|
91201
91376
|
gte23(researchRuns.createdAt, start),
|
|
91202
91377
|
lt14(researchRuns.createdAt, end),
|
|
91203
91378
|
sql31`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -91251,10 +91426,10 @@ async function researchRoutes(app, opts) {
|
|
|
91251
91426
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
91252
91427
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
91253
91428
|
const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
|
|
91254
|
-
const rows = app.db.select().from(researchRuns).where(
|
|
91255
|
-
|
|
91256
|
-
cursor ?
|
|
91257
|
-
)).orderBy(
|
|
91429
|
+
const rows = app.db.select().from(researchRuns).where(and73(
|
|
91430
|
+
eq87(researchRuns.projectId, project.id),
|
|
91431
|
+
cursor ? or22(lt14(researchRuns.createdAt, cursor.createdAt), and73(eq87(researchRuns.createdAt, cursor.createdAt), lt14(researchRuns.id, cursor.id))) : void 0
|
|
91432
|
+
)).orderBy(desc41(researchRuns.createdAt), desc41(researchRuns.id)).limit(limit + 1).all();
|
|
91258
91433
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
91259
91434
|
const last = runs2.at(-1);
|
|
91260
91435
|
const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
|
|
@@ -91380,9 +91555,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
91380
91555
|
return stored;
|
|
91381
91556
|
}
|
|
91382
91557
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
91383
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
91384
|
-
|
|
91385
|
-
|
|
91558
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and73(
|
|
91559
|
+
eq87(measurementQueryTemplates.projectId, projectId),
|
|
91560
|
+
eq87(measurementQueryTemplates.id, selection.templateId)
|
|
91386
91561
|
)).get();
|
|
91387
91562
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
91388
91563
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -91391,7 +91566,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
91391
91566
|
}
|
|
91392
91567
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
91393
91568
|
if (bindingLocation) {
|
|
91394
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
91569
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq87(projects.id, projectId)).get();
|
|
91395
91570
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
91396
91571
|
}
|
|
91397
91572
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -91404,9 +91579,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
91404
91579
|
return queries2.map((query) => classifier?.classify(query) ?? null);
|
|
91405
91580
|
}
|
|
91406
91581
|
function getDetail(app, projectId, id) {
|
|
91407
|
-
const row = app.db.select().from(researchRuns).where(
|
|
91582
|
+
const row = app.db.select().from(researchRuns).where(and73(eq87(researchRuns.id, id), eq87(researchRuns.projectId, projectId))).get();
|
|
91408
91583
|
if (!row) throw notFound("Research run", id);
|
|
91409
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
91584
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq87(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
91410
91585
|
return { ...serializeRun(row), queries: queries2 };
|
|
91411
91586
|
}
|
|
91412
91587
|
function serializeRun(row) {
|
|
@@ -91436,7 +91611,7 @@ function utcDayBounds(now) {
|
|
|
91436
91611
|
|
|
91437
91612
|
// ../api-routes/src/snapshot-competitor-refresh.ts
|
|
91438
91613
|
import { setImmediate as yieldToEventLoop2 } from "timers/promises";
|
|
91439
|
-
import { and as
|
|
91614
|
+
import { and as and74, asc as asc17, eq as eq88, inArray as inArray36 } from "drizzle-orm";
|
|
91440
91615
|
var ANSWER_MENTIONS_CHUNK_SIZE = 50;
|
|
91441
91616
|
var ANSWER_MENTIONS_MAX_RESTARTS = 3;
|
|
91442
91617
|
function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
@@ -91486,14 +91661,14 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
|
|
|
91486
91661
|
examined = 0;
|
|
91487
91662
|
wouldUpdate = 0;
|
|
91488
91663
|
mentioned = 0;
|
|
91489
|
-
const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(
|
|
91490
|
-
|
|
91491
|
-
|
|
91492
|
-
opts?.runId === void 0 ? void 0 :
|
|
91664
|
+
const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and74(
|
|
91665
|
+
eq88(runs.kind, RunKinds["answer-visibility"]),
|
|
91666
|
+
eq88(runs.projectId, projectId),
|
|
91667
|
+
opts?.runId === void 0 ? void 0 : eq88(runs.id, opts.runId)
|
|
91493
91668
|
)).all();
|
|
91494
91669
|
const planVersionByRun = new Map(runRows.map((run) => [run.id, run.planVersionId]));
|
|
91495
91670
|
for (const run of runRows) {
|
|
91496
|
-
const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
91671
|
+
const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(eq88(querySnapshots.runId, run.id)).orderBy(asc17(querySnapshots.id)).all().map((row) => row.id);
|
|
91497
91672
|
for (let offset = 0; offset < snapshotIds.length; offset += ANSWER_MENTIONS_CHUNK_SIZE) {
|
|
91498
91673
|
if (chunks++ > 0 && driver.recheckIdentity) {
|
|
91499
91674
|
yield;
|
|
@@ -91519,7 +91694,7 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
|
|
|
91519
91694
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
91520
91695
|
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
91521
91696
|
rawResponse: querySnapshots.rawResponse
|
|
91522
|
-
}).from(querySnapshots).where(
|
|
91697
|
+
}).from(querySnapshots).where(inArray36(querySnapshots.id, snapshotIds.slice(offset, offset + ANSWER_MENTIONS_CHUNK_SIZE))).all();
|
|
91523
91698
|
const pendingUpdates = [];
|
|
91524
91699
|
for (const snapshot of snapshotRows) {
|
|
91525
91700
|
if (competitorFieldsOnly && snapshot.answerText == null) continue;
|
|
@@ -91569,7 +91744,7 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
|
|
|
91569
91744
|
} else {
|
|
91570
91745
|
db.transaction((tx) => {
|
|
91571
91746
|
for (const update of pendingUpdates) {
|
|
91572
|
-
tx.update(querySnapshots).set(update.patch).where(
|
|
91747
|
+
tx.update(querySnapshots).set(update.patch).where(eq88(querySnapshots.id, update.id)).run();
|
|
91573
91748
|
}
|
|
91574
91749
|
});
|
|
91575
91750
|
updated += pendingUpdates.length;
|
|
@@ -91599,7 +91774,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
91599
91774
|
|
|
91600
91775
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
91601
91776
|
import crypto54 from "crypto";
|
|
91602
|
-
import { and as
|
|
91777
|
+
import { and as and75, eq as eq89 } from "drizzle-orm";
|
|
91603
91778
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
91604
91779
|
return db.transaction((tx) => {
|
|
91605
91780
|
const run = tx.select({
|
|
@@ -91607,7 +91782,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
91607
91782
|
trigger: runs.trigger,
|
|
91608
91783
|
status: runs.status,
|
|
91609
91784
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
91610
|
-
}).from(runs).where(
|
|
91785
|
+
}).from(runs).where(and75(eq89(runs.projectId, input.projectId), eq89(runs.id, input.runId))).get();
|
|
91611
91786
|
if (!run) throw notFound("Run", input.runId);
|
|
91612
91787
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
91613
91788
|
return null;
|
|
@@ -91628,7 +91803,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
91628
91803
|
throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
|
|
91629
91804
|
}
|
|
91630
91805
|
}
|
|
91631
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
91806
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and75(eq89(simpleMeasurementDefinitions.projectId, input.projectId), eq89(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
91632
91807
|
if (existing) {
|
|
91633
91808
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
91634
91809
|
const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition.identity;
|
|
@@ -91654,15 +91829,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
91654
91829
|
if (run.status !== RunStatuses.running) {
|
|
91655
91830
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
91656
91831
|
}
|
|
91657
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
91832
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq89(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
91658
91833
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
91659
91834
|
}
|
|
91660
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
91835
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq89(queries.projectId, input.projectId)).all().map((query) => query.id));
|
|
91661
91836
|
if (definition.queries.some((query) => !projectQueryIds.has(query.queryId))) {
|
|
91662
91837
|
throw validationError("Every captured query must belong to the run project.");
|
|
91663
91838
|
}
|
|
91664
91839
|
if (definition.competitors !== void 0) {
|
|
91665
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
91840
|
+
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq89(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
|
|
91666
91841
|
const capturedDomains = new Set(definition.competitors.map((competitor) => competitor.domain));
|
|
91667
91842
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
91668
91843
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|
|
@@ -92530,10 +92705,10 @@ var log = createLogger("IntelligenceService");
|
|
|
92530
92705
|
var runChronologyKey = sql32`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
|
|
92531
92706
|
function analyzableRunPredicates(projectId) {
|
|
92532
92707
|
return [
|
|
92533
|
-
|
|
92708
|
+
eq90(runs.projectId, projectId),
|
|
92534
92709
|
// Only this kind writes query_snapshots.
|
|
92535
|
-
|
|
92536
|
-
|
|
92710
|
+
eq90(runs.kind, RunKinds["answer-visibility"]),
|
|
92711
|
+
or23(eq90(runs.status, RunStatuses.completed), eq90(runs.status, RunStatuses.partial)),
|
|
92537
92712
|
// Defensive: RunCoordinator already skips probes before analyzeAndPersist
|
|
92538
92713
|
// is called, but a future call site invoking it directly for a probe must
|
|
92539
92714
|
// still not pollute the intelligence window.
|
|
@@ -92578,16 +92753,16 @@ var IntelligenceService = class {
|
|
|
92578
92753
|
* Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
|
|
92579
92754
|
*/
|
|
92580
92755
|
analyzeAndPersist(runId, projectId) {
|
|
92581
|
-
const currentRunRecord = this.db.select().from(runs).where(
|
|
92756
|
+
const currentRunRecord = this.db.select().from(runs).where(and76(eq90(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
|
|
92582
92757
|
if (!currentRunRecord) {
|
|
92583
92758
|
log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
|
|
92584
92759
|
return null;
|
|
92585
92760
|
}
|
|
92586
92761
|
const currentLocation = currentRunRecord.location ?? null;
|
|
92587
92762
|
const recentRuns = this.db.select().from(runs).where(
|
|
92588
|
-
|
|
92763
|
+
and76(
|
|
92589
92764
|
...analyzableRunPredicates(projectId),
|
|
92590
|
-
currentLocation === null ? isNull17(runs.location) :
|
|
92765
|
+
currentLocation === null ? isNull17(runs.location) : eq90(runs.location, currentLocation),
|
|
92591
92766
|
// Never look forward. The window is this run's own past, so
|
|
92592
92767
|
// re-analyzing a historical run compares it against its true
|
|
92593
92768
|
// predecessor rather than against sweeps that came after it.
|
|
@@ -92595,10 +92770,10 @@ var IntelligenceService = class {
|
|
|
92595
92770
|
// MEASUREMENT: only runs that actually wrote snapshots — see above.
|
|
92596
92771
|
// Index seek on idx_snapshots_run, short-circuited on the first row.
|
|
92597
92772
|
exists2(
|
|
92598
|
-
this.db.select({ one: sql32`1` }).from(querySnapshots).where(
|
|
92773
|
+
this.db.select({ one: sql32`1` }).from(querySnapshots).where(eq90(querySnapshots.runId, runs.id))
|
|
92599
92774
|
)
|
|
92600
92775
|
)
|
|
92601
|
-
).orderBy(
|
|
92776
|
+
).orderBy(desc42(runs.finishedAt), desc42(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
|
|
92602
92777
|
const trackedCompetitors = this.loadTrackedCompetitors(projectId);
|
|
92603
92778
|
const trackedCompetitorDomains = trackedCompetitors.map((competitor) => competitor.domain);
|
|
92604
92779
|
const currentRun = this.buildRunData(
|
|
@@ -92671,7 +92846,7 @@ var IntelligenceService = class {
|
|
|
92671
92846
|
* Returns the persisted insights so the coordinator can count critical/high.
|
|
92672
92847
|
*/
|
|
92673
92848
|
analyzeAndPersistGbp(runId, projectId) {
|
|
92674
|
-
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
92849
|
+
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq90(runs.id, runId)).get();
|
|
92675
92850
|
if (!runRow) {
|
|
92676
92851
|
log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
|
|
92677
92852
|
this.persistGbpInsights(runId, projectId, [], []);
|
|
@@ -92679,9 +92854,9 @@ var IntelligenceService = class {
|
|
|
92679
92854
|
}
|
|
92680
92855
|
const windowStart = runRow.startedAt ?? runRow.createdAt;
|
|
92681
92856
|
const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
92682
|
-
const selected = this.db.select().from(gbpLocations).where(
|
|
92683
|
-
|
|
92684
|
-
|
|
92857
|
+
const selected = this.db.select().from(gbpLocations).where(and76(
|
|
92858
|
+
eq90(gbpLocations.projectId, projectId),
|
|
92859
|
+
eq90(gbpLocations.selected, true),
|
|
92685
92860
|
gte24(gbpLocations.syncedAt, windowStart),
|
|
92686
92861
|
lte20(gbpLocations.syncedAt, windowEnd)
|
|
92687
92862
|
)).all();
|
|
@@ -92716,12 +92891,12 @@ var IntelligenceService = class {
|
|
|
92716
92891
|
}
|
|
92717
92892
|
/** Build the per-location signal bundle the GBP analyzer consumes. */
|
|
92718
92893
|
buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
|
|
92719
|
-
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(
|
|
92720
|
-
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(
|
|
92721
|
-
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(
|
|
92722
|
-
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(
|
|
92894
|
+
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and76(eq90(gbpDailyMetrics.projectId, projectId), eq90(gbpDailyMetrics.locationName, locationName))).all();
|
|
92895
|
+
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and76(eq90(gbpPlaceActions.projectId, projectId), eq90(gbpPlaceActions.locationName, locationName))).all();
|
|
92896
|
+
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and76(eq90(gbpLodgingSnapshots.projectId, projectId), eq90(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc42(gbpLodgingSnapshots.syncedAt)).limit(1).get();
|
|
92897
|
+
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and76(eq90(gbpLocations.projectId, projectId), eq90(gbpLocations.locationName, locationName))).get();
|
|
92723
92898
|
const descriptionMissing = !(ownerRow?.description ?? "").trim();
|
|
92724
|
-
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(
|
|
92899
|
+
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and76(eq90(gbpPlaceDetails.projectId, projectId), eq90(gbpPlaceDetails.locationName, locationName))).orderBy(desc42(gbpPlaceDetails.syncedAt)).limit(1).get();
|
|
92725
92900
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
92726
92901
|
const summary3 = buildGbpSummary({
|
|
92727
92902
|
locationName,
|
|
@@ -92757,7 +92932,7 @@ var IntelligenceService = class {
|
|
|
92757
92932
|
/** Build the month-over-month keyword series for a location from the
|
|
92758
92933
|
* accumulating gbp_keyword_monthly table (latest complete month vs prior). */
|
|
92759
92934
|
buildGbpKeywordTrend(projectId, locationName) {
|
|
92760
|
-
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(
|
|
92935
|
+
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and76(eq90(gbpKeywordMonthly.projectId, projectId), eq90(gbpKeywordMonthly.locationName, locationName))).all();
|
|
92761
92936
|
if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
|
|
92762
92937
|
const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
|
|
92763
92938
|
const recentMonth = months[0] ?? null;
|
|
@@ -92788,7 +92963,7 @@ var IntelligenceService = class {
|
|
|
92788
92963
|
*/
|
|
92789
92964
|
persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
|
|
92790
92965
|
const covered = new Set(coveredLocationNames);
|
|
92791
|
-
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(
|
|
92966
|
+
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and76(eq90(insights.projectId, projectId), eq90(insights.provider, GBP_INSIGHT_PROVIDER))).all();
|
|
92792
92967
|
const staleIds = [];
|
|
92793
92968
|
const dismissedSlots = /* @__PURE__ */ new Set();
|
|
92794
92969
|
for (const row of existing) {
|
|
@@ -92799,7 +92974,7 @@ var IntelligenceService = class {
|
|
|
92799
92974
|
}
|
|
92800
92975
|
this.db.transaction((tx) => {
|
|
92801
92976
|
for (const id of staleIds) {
|
|
92802
|
-
tx.delete(insights).where(
|
|
92977
|
+
tx.delete(insights).where(eq90(insights.id, id)).run();
|
|
92803
92978
|
}
|
|
92804
92979
|
for (const insight of gbpInsights) {
|
|
92805
92980
|
const parsed = parseGbpInsightId(insight.id);
|
|
@@ -92886,7 +93061,7 @@ var IntelligenceService = class {
|
|
|
92886
93061
|
* create per run + aggregate). DB is left untouched.
|
|
92887
93062
|
*/
|
|
92888
93063
|
backfill(projectName, opts, onProgress) {
|
|
92889
|
-
const project = this.db.select().from(projects).where(
|
|
93064
|
+
const project = this.db.select().from(projects).where(eq90(projects.name, projectName)).get();
|
|
92890
93065
|
if (!project) {
|
|
92891
93066
|
throw new Error(`Project "${projectName}" not found`);
|
|
92892
93067
|
}
|
|
@@ -92898,7 +93073,7 @@ var IntelligenceService = class {
|
|
|
92898
93073
|
}
|
|
92899
93074
|
sinceTimestamp = parsed;
|
|
92900
93075
|
}
|
|
92901
|
-
const allRuns = this.db.select().from(runs).where(
|
|
93076
|
+
const allRuns = this.db.select().from(runs).where(and76(...analyzableRunPredicates(project.id))).orderBy(asc18(runs.finishedAt)).all();
|
|
92902
93077
|
const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
|
|
92903
93078
|
const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
|
|
92904
93079
|
let startIdx = 0;
|
|
@@ -92929,7 +93104,7 @@ var IntelligenceService = class {
|
|
|
92929
93104
|
let wouldDeleteTotal = 0;
|
|
92930
93105
|
const existingByRunId = /* @__PURE__ */ new Map();
|
|
92931
93106
|
if (isDryRun && targetRuns.length > 0) {
|
|
92932
|
-
const rows = this.db.select({ runId: insights.runId }).from(insights).where(
|
|
93107
|
+
const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray37(insights.runId, targetRuns.map((r) => r.id))).all();
|
|
92933
93108
|
for (const r of rows) {
|
|
92934
93109
|
if (r.runId == null) continue;
|
|
92935
93110
|
existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
|
|
@@ -92980,7 +93155,7 @@ var IntelligenceService = class {
|
|
|
92980
93155
|
* signals match the same identities the dashboard and reports do.
|
|
92981
93156
|
*/
|
|
92982
93157
|
loadTrackedCompetitors(projectId) {
|
|
92983
|
-
return this.db.select(competitorIdentityColumns).from(competitors).where(
|
|
93158
|
+
return this.db.select(competitorIdentityColumns).from(competitors).where(eq90(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
92984
93159
|
}
|
|
92985
93160
|
/**
|
|
92986
93161
|
* Which of `runIds` actually wrote query snapshots. One grouped read, so
|
|
@@ -92989,7 +93164,7 @@ var IntelligenceService = class {
|
|
|
92989
93164
|
*/
|
|
92990
93165
|
runIdsWithSnapshots(runIds) {
|
|
92991
93166
|
if (runIds.length === 0) return /* @__PURE__ */ new Set();
|
|
92992
|
-
const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(
|
|
93167
|
+
const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray37(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
|
|
92993
93168
|
return new Set(rows.map((r) => r.runId));
|
|
92994
93169
|
}
|
|
92995
93170
|
/**
|
|
@@ -93011,15 +93186,15 @@ var IntelligenceService = class {
|
|
|
93011
93186
|
}
|
|
93012
93187
|
persistResult(result, runId, projectId) {
|
|
93013
93188
|
const previouslyDismissed = /* @__PURE__ */ new Set();
|
|
93014
|
-
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(
|
|
93189
|
+
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq90(insights.runId, runId)).all();
|
|
93015
93190
|
for (const row of existingInsights) {
|
|
93016
93191
|
if (row.dismissed) {
|
|
93017
93192
|
previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
|
|
93018
93193
|
}
|
|
93019
93194
|
}
|
|
93020
93195
|
this.db.transaction((tx) => {
|
|
93021
|
-
tx.delete(insights).where(
|
|
93022
|
-
tx.delete(healthSnapshots).where(
|
|
93196
|
+
tx.delete(insights).where(eq90(insights.runId, runId)).run();
|
|
93197
|
+
tx.delete(healthSnapshots).where(eq90(healthSnapshots.runId, runId)).run();
|
|
93023
93198
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
93024
93199
|
for (const insight of result.insights) {
|
|
93025
93200
|
const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
|
|
@@ -93066,8 +93241,8 @@ var IntelligenceService = class {
|
|
|
93066
93241
|
clicks: gscSearchData.clicks,
|
|
93067
93242
|
impressions: gscSearchData.impressions,
|
|
93068
93243
|
position: gscSearchData.position
|
|
93069
|
-
}).from(gscSearchData).where(
|
|
93070
|
-
|
|
93244
|
+
}).from(gscSearchData).where(and76(
|
|
93245
|
+
eq90(gscSearchData.projectId, projectId),
|
|
93071
93246
|
gte24(gscSearchData.date, window.startDate),
|
|
93072
93247
|
lte20(gscSearchData.date, window.endDate)
|
|
93073
93248
|
)).all();
|
|
@@ -93120,21 +93295,21 @@ var IntelligenceService = class {
|
|
|
93120
93295
|
const key = row.query.toLowerCase();
|
|
93121
93296
|
gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
|
|
93122
93297
|
}
|
|
93123
|
-
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
93298
|
+
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq90(projects.id, projectId)).get();
|
|
93124
93299
|
const locationCount = Math.max(
|
|
93125
93300
|
1,
|
|
93126
93301
|
(projectRow?.locations ?? []).length
|
|
93127
93302
|
);
|
|
93128
93303
|
const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
|
|
93129
93304
|
const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
93130
|
-
|
|
93131
|
-
|
|
93132
|
-
|
|
93133
|
-
|
|
93305
|
+
and76(
|
|
93306
|
+
eq90(runs.projectId, projectId),
|
|
93307
|
+
eq90(runs.kind, RunKinds["answer-visibility"]),
|
|
93308
|
+
or23(eq90(runs.status, "completed"), eq90(runs.status, "partial")),
|
|
93134
93309
|
// Defensive — see top of file.
|
|
93135
93310
|
ne16(runs.trigger, RunTriggers.probe)
|
|
93136
93311
|
)
|
|
93137
|
-
).orderBy(
|
|
93312
|
+
).orderBy(desc42(runs.createdAt), desc42(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
|
|
93138
93313
|
const recentGroups = groupRunsByCreatedAt(recentRunRows);
|
|
93139
93314
|
const recentRunIds = [];
|
|
93140
93315
|
const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
|
|
@@ -93150,7 +93325,7 @@ var IntelligenceService = class {
|
|
|
93150
93325
|
const haveHistory = recentRunIds.length > 0;
|
|
93151
93326
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
93152
93327
|
if (haveHistory) {
|
|
93153
|
-
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(
|
|
93328
|
+
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and76(eq90(insights.type, "regression"), inArray37(insights.runId, recentRunIds))).all();
|
|
93154
93329
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
93155
93330
|
for (const row of priorRows) {
|
|
93156
93331
|
if (!row.runId) continue;
|
|
@@ -93179,7 +93354,7 @@ var IntelligenceService = class {
|
|
|
93179
93354
|
});
|
|
93180
93355
|
}
|
|
93181
93356
|
buildRunData(runId, projectId, completedAt, location = null, trackedCompetitors = []) {
|
|
93182
|
-
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(
|
|
93357
|
+
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq90(projects.id, projectId)).get();
|
|
93183
93358
|
const projectDomains = projectDomainRow ? effectiveDomains({
|
|
93184
93359
|
canonicalDomain: projectDomainRow.canonicalDomain,
|
|
93185
93360
|
ownedDomains: projectDomainRow.ownedDomains
|
|
@@ -93197,7 +93372,7 @@ var IntelligenceService = class {
|
|
|
93197
93372
|
citedDomains: querySnapshots.citedDomains,
|
|
93198
93373
|
rawResponse: querySnapshots.rawResponse,
|
|
93199
93374
|
snapshotLocation: querySnapshots.location
|
|
93200
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
93375
|
+
}).from(querySnapshots).leftJoin(queries, eq90(querySnapshots.queryId, queries.id)).where(eq90(querySnapshots.runId, runId)).all();
|
|
93201
93376
|
const snapshots = [];
|
|
93202
93377
|
let orphanCount = 0;
|
|
93203
93378
|
for (const r of rows) {
|