@canonry/canonry 7.24.0 → 7.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +1 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +6 -0
- 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-DKKnX8IT.js → AuditHistoryPanel-B-9DLSmD.js} +1 -1
- package/assets/assets/{BacklinksPage-DNbur8am.js → BacklinksPage-DPssWU5-.js} +1 -1
- package/assets/assets/{HistoryPage-DahtHlpM.js → HistoryPage-BAPUoHjI.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-BoNDtLfp.js → MeasurementPropertyPage-aTs5l1OV.js} +1 -1
- package/assets/assets/{ProjectPage--ZlpQduH.js → ProjectPage-sVY1ETmt.js} +1 -1
- package/assets/assets/{RunsPage-DKB2CHTM.js → RunsPage-D0q7ifK3.js} +1 -1
- package/assets/assets/{SettingsPage-lnmMZzEj.js → SettingsPage-Oo861eRm.js} +1 -1
- package/assets/assets/{SiteHealthSection-BBjucRed.js → SiteHealthSection-BMdk3Och.js} +3 -3
- package/assets/assets/{TrafficPage-DNyTmPLd.js → TrafficPage-Ciot_0lN.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-D1zKZHSh.js → TrafficSourceDetailPage-D7tWadfJ.js} +1 -1
- package/assets/assets/{extract-error-message-v0b8P-wc.js → extract-error-message-Bb4azvlK.js} +1 -1
- package/assets/assets/{index-CfaBRiBG.js → index-ClA5Nf2V.js} +18 -18
- package/assets/assets/{react-sigma_core.esm.min-BZSJoRrV.js → react-sigma_core.esm.min-C-Y9Z74U.js} +1 -1
- package/assets/assets/{v2-overview-adapter-DXAl-O_h.js → v2-overview-adapter-D2S25ivE.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-DS2ZKB3T.js → chunk-6AOIFHC6.js} +1 -1
- package/dist/{chunk-JJBVYL45.js → chunk-BMBHREYM.js} +2 -2
- package/dist/{chunk-7Q7QCBJ3.js → chunk-I3546GTK.js} +2 -2
- package/dist/{chunk-YCPTP2NC.js → chunk-NOA7KJDB.js} +3 -3
- package/dist/{chunk-SG3MPCDL.js → chunk-NX257AKC.js} +145 -25
- package/dist/{chunk-IM2BFAAT.js → chunk-PFEQWGJN.js} +7 -5
- package/dist/{chunk-O5UMRQVN.js → chunk-PKQF7BV2.js} +759 -610
- package/dist/cli.js +14 -14
- package/dist/{demo-server-C7UGXXN7.js → demo-server-RUZWYXSW.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-37TX4YV7.js → intelligence-service-TYSVXO3S.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +11 -11
|
@@ -864,10 +864,10 @@ import {
|
|
|
864
864
|
wordpressSchemaDeployResultDtoSchema,
|
|
865
865
|
wordpressSchemaStatusResultDtoSchema,
|
|
866
866
|
wordpressStatusDtoSchema
|
|
867
|
-
} from "./chunk-
|
|
867
|
+
} from "./chunk-PFEQWGJN.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})`,
|
|
@@ -47459,7 +47543,9 @@ async function telemetryRoutes(app, opts) {
|
|
|
47459
47543
|
opts.recordOnboardingEvent?.(normalizeOnboardingEventForCollection(parsed.data));
|
|
47460
47544
|
return reply.status(202).send({ accepted: Boolean(opts.recordOnboardingEvent) });
|
|
47461
47545
|
});
|
|
47462
|
-
app.post("/telemetry/ui",
|
|
47546
|
+
app.post("/telemetry/ui", {
|
|
47547
|
+
config: { readSemantic: true }
|
|
47548
|
+
}, async (request, reply) => {
|
|
47463
47549
|
const parsed = uiTelemetryEventSchema.safeParse(request.body);
|
|
47464
47550
|
if (!parsed.success) {
|
|
47465
47551
|
throw validationError("Invalid UI telemetry event", {
|
|
@@ -62962,7 +63048,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
62962
63048
|
|
|
62963
63049
|
// ../api-routes/src/backlinks.ts
|
|
62964
63050
|
import crypto45 from "crypto";
|
|
62965
|
-
import { and as
|
|
63051
|
+
import { and as and57, asc as asc14, desc as desc34, eq as eq66, sql as sql25 } from "drizzle-orm";
|
|
62966
63052
|
|
|
62967
63053
|
// ../integration-commoncrawl/src/constants.ts
|
|
62968
63054
|
import os2 from "os";
|
|
@@ -63393,6 +63479,22 @@ function backlinkCrawlerExclusionClause() {
|
|
|
63393
63479
|
return combined;
|
|
63394
63480
|
}
|
|
63395
63481
|
|
|
63482
|
+
// ../api-routes/src/backlinks-source-state.ts
|
|
63483
|
+
import { and as and56, eq as eq65, isNotNull as isNotNull8, or as or17 } from "drizzle-orm";
|
|
63484
|
+
function readCommonCrawlSourceState(db, projectId) {
|
|
63485
|
+
const completedReleases = db.select({ status: ccReleaseSyncs.status }).from(ccReleaseSyncs).where(or17(
|
|
63486
|
+
eq65(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready),
|
|
63487
|
+
isNotNull8(ccReleaseSyncs.queryFinishedAt)
|
|
63488
|
+
)).all();
|
|
63489
|
+
const hasReadyRelease = completedReleases.length > 0;
|
|
63490
|
+
const hasCurrentReadyRelease = completedReleases.some((release) => release.status === CcReleaseSyncStatuses.ready);
|
|
63491
|
+
const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and56(
|
|
63492
|
+
eq65(backlinkSummaries.projectId, projectId),
|
|
63493
|
+
eq65(backlinkSummaries.source, BacklinkSources.commoncrawl)
|
|
63494
|
+
)).limit(1).get() !== void 0;
|
|
63495
|
+
return { hasReadyRelease, hasCurrentReadyRelease, hasProjectData, connected: hasReadyRelease && hasProjectData };
|
|
63496
|
+
}
|
|
63497
|
+
|
|
63396
63498
|
// ../api-routes/src/backlinks.ts
|
|
63397
63499
|
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.";
|
|
63398
63500
|
var NON_TERMINAL_SYNC_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -63459,11 +63561,11 @@ function mapRunRow(row) {
|
|
|
63459
63561
|
}
|
|
63460
63562
|
function latestSummaryForProject(db, projectId, source, release) {
|
|
63461
63563
|
const conditions = [
|
|
63462
|
-
|
|
63463
|
-
|
|
63564
|
+
eq66(backlinkSummaries.projectId, projectId),
|
|
63565
|
+
eq66(backlinkSummaries.source, source)
|
|
63464
63566
|
];
|
|
63465
|
-
if (release) conditions.push(
|
|
63466
|
-
return db.select().from(backlinkSummaries).where(
|
|
63567
|
+
if (release) conditions.push(eq66(backlinkSummaries.release, release));
|
|
63568
|
+
return db.select().from(backlinkSummaries).where(and57(...conditions)).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
|
|
63467
63569
|
}
|
|
63468
63570
|
function parseExcludeCrawlers(value) {
|
|
63469
63571
|
if (!value) return false;
|
|
@@ -63471,12 +63573,12 @@ function parseExcludeCrawlers(value) {
|
|
|
63471
63573
|
return lower === "1" || lower === "true" || lower === "yes";
|
|
63472
63574
|
}
|
|
63473
63575
|
function computeFilteredSummary(db, base) {
|
|
63474
|
-
const baseDomainCondition =
|
|
63475
|
-
|
|
63476
|
-
|
|
63477
|
-
|
|
63576
|
+
const baseDomainCondition = and57(
|
|
63577
|
+
eq66(backlinkDomains.projectId, base.projectId),
|
|
63578
|
+
eq66(backlinkDomains.source, base.source),
|
|
63579
|
+
eq66(backlinkDomains.release, base.release)
|
|
63478
63580
|
);
|
|
63479
|
-
const filteredCondition =
|
|
63581
|
+
const filteredCondition = and57(baseDomainCondition, backlinkCrawlerExclusionClause());
|
|
63480
63582
|
const unfilteredAgg = db.select({
|
|
63481
63583
|
count: sql25`count(*)`,
|
|
63482
63584
|
total: sql25`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
@@ -63506,13 +63608,13 @@ function computeFilteredSummary(db, base) {
|
|
|
63506
63608
|
};
|
|
63507
63609
|
}
|
|
63508
63610
|
function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
|
|
63509
|
-
const summary3 = db.select().from(backlinkSummaries).where(
|
|
63611
|
+
const summary3 = db.select().from(backlinkSummaries).where(and57(eq66(backlinkSummaries.projectId, projectId), eq66(backlinkSummaries.source, source))).orderBy(desc34(backlinkSummaries.queriedAt)).limit(1).get();
|
|
63510
63612
|
let totalLinkingDomains = summary3?.totalLinkingDomains ?? 0;
|
|
63511
63613
|
if (summary3 && excludeCrawlers) {
|
|
63512
|
-
const filtered = db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(
|
|
63513
|
-
|
|
63514
|
-
|
|
63515
|
-
|
|
63614
|
+
const filtered = db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(and57(
|
|
63615
|
+
eq66(backlinkDomains.projectId, projectId),
|
|
63616
|
+
eq66(backlinkDomains.source, source),
|
|
63617
|
+
eq66(backlinkDomains.release, summary3.release),
|
|
63516
63618
|
backlinkCrawlerExclusionClause()
|
|
63517
63619
|
)).get();
|
|
63518
63620
|
totalLinkingDomains = Number(filtered?.count ?? 0);
|
|
@@ -63527,8 +63629,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
|
|
|
63527
63629
|
};
|
|
63528
63630
|
}
|
|
63529
63631
|
function computeSourceAvailability(db, project, excludeCrawlers) {
|
|
63530
|
-
const
|
|
63531
|
-
const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
|
|
63632
|
+
const ccConnected = readCommonCrawlSourceState(db, project.id).connected;
|
|
63532
63633
|
const sources = [
|
|
63533
63634
|
buildSourceAvailability(db, project.id, BacklinkSources.commoncrawl, ccConnected, excludeCrawlers),
|
|
63534
63635
|
buildSourceAvailability(db, project.id, BacklinkSources["bing-webmaster"], false, excludeCrawlers)
|
|
@@ -63583,7 +63684,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63583
63684
|
"@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
|
|
63584
63685
|
);
|
|
63585
63686
|
}
|
|
63586
|
-
const existing = app.db.select().from(ccReleaseSyncs).where(
|
|
63687
|
+
const existing = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.release, release)).get();
|
|
63587
63688
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
63588
63689
|
if (existing) {
|
|
63589
63690
|
if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
|
|
@@ -63594,9 +63695,9 @@ async function backlinksRoutes(app, opts) {
|
|
|
63594
63695
|
phaseDetail: null,
|
|
63595
63696
|
error: null,
|
|
63596
63697
|
updatedAt: now
|
|
63597
|
-
}).where(
|
|
63698
|
+
}).where(eq66(ccReleaseSyncs.id, existing.id)).run();
|
|
63598
63699
|
opts.onReleaseSyncRequested(existing.id, release);
|
|
63599
|
-
const refreshed = app.db.select().from(ccReleaseSyncs).where(
|
|
63700
|
+
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.id, existing.id)).get();
|
|
63600
63701
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
63601
63702
|
}
|
|
63602
63703
|
const id = crypto45.randomUUID();
|
|
@@ -63608,7 +63709,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63608
63709
|
updatedAt: now
|
|
63609
63710
|
}).run();
|
|
63610
63711
|
opts.onReleaseSyncRequested(id, release);
|
|
63611
|
-
const inserted = app.db.select().from(ccReleaseSyncs).where(
|
|
63712
|
+
const inserted = app.db.select().from(ccReleaseSyncs).where(eq66(ccReleaseSyncs.id, id)).get();
|
|
63612
63713
|
return reply.status(201).send(mapSyncRow(inserted));
|
|
63613
63714
|
});
|
|
63614
63715
|
app.get("/backlinks/syncs/latest", async (_request, reply) => {
|
|
@@ -63666,7 +63767,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63666
63767
|
createdAt: now
|
|
63667
63768
|
}).run();
|
|
63668
63769
|
opts.onBacklinkExtractRequested(runId, project.id, release);
|
|
63669
|
-
const run = app.db.select().from(runs).where(
|
|
63770
|
+
const run = app.db.select().from(runs).where(eq66(runs.id, runId)).get();
|
|
63670
63771
|
return reply.status(201).send(mapRunRow(run));
|
|
63671
63772
|
});
|
|
63672
63773
|
app.get(
|
|
@@ -63692,12 +63793,12 @@ async function backlinksRoutes(app, opts) {
|
|
|
63692
63793
|
const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
|
|
63693
63794
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
63694
63795
|
const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
|
|
63695
|
-
const baseDomainCondition =
|
|
63696
|
-
|
|
63697
|
-
|
|
63698
|
-
|
|
63796
|
+
const baseDomainCondition = and57(
|
|
63797
|
+
eq66(backlinkDomains.projectId, project.id),
|
|
63798
|
+
eq66(backlinkDomains.source, source),
|
|
63799
|
+
eq66(backlinkDomains.release, targetRelease)
|
|
63699
63800
|
);
|
|
63700
|
-
const domainCondition = excludeCrawlers ?
|
|
63801
|
+
const domainCondition = excludeCrawlers ? and57(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
|
|
63701
63802
|
const totalRow = app.db.select({ count: sql25`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
63702
63803
|
const rows = app.db.select({
|
|
63703
63804
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
@@ -63721,7 +63822,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63721
63822
|
async (request, reply) => {
|
|
63722
63823
|
const project = resolveProject(app.db, request.params.name);
|
|
63723
63824
|
const source = parseSourceParam(request.query.source);
|
|
63724
|
-
const rows = app.db.select().from(backlinkSummaries).where(
|
|
63825
|
+
const rows = app.db.select().from(backlinkSummaries).where(and57(eq66(backlinkSummaries.projectId, project.id), eq66(backlinkSummaries.source, source))).orderBy(asc14(backlinkSummaries.queriedAt)).all();
|
|
63725
63826
|
const response = rows.map((r) => ({
|
|
63726
63827
|
release: r.release,
|
|
63727
63828
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -63748,7 +63849,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63748
63849
|
import crypto48 from "crypto";
|
|
63749
63850
|
import { isIP } from "net";
|
|
63750
63851
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
63751
|
-
import { and as
|
|
63852
|
+
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";
|
|
63752
63853
|
|
|
63753
63854
|
// ../api-routes/src/cloudflare-worker-version.ts
|
|
63754
63855
|
var CURRENT_CLOUDFLARE_WORKER_VERSION = "1.0.1";
|
|
@@ -80414,10 +80515,10 @@ async function ackCloudflareQueueMessages(client, options) {
|
|
|
80414
80515
|
|
|
80415
80516
|
// ../api-routes/src/traffic-event-ingest.ts
|
|
80416
80517
|
import crypto47 from "crypto";
|
|
80417
|
-
import { and as
|
|
80518
|
+
import { and as and59, eq as eq68, gte as gte15, lt as lt12, lte as lte15, sql as sql27 } from "drizzle-orm";
|
|
80418
80519
|
|
|
80419
80520
|
// ../api-routes/src/raw-event-sample-retention.ts
|
|
80420
|
-
import { and as
|
|
80521
|
+
import { and as and58, eq as eq67, lt as lt11, sql as sql26 } from "drizzle-orm";
|
|
80421
80522
|
var RAW_EVENT_SAMPLE_RETENTION_MS = 30 * 24 * 60 * 6e4;
|
|
80422
80523
|
var RAW_EVENT_SAMPLE_RETENTION_SWEEP_INTERVAL_MS = 24 * 60 * 6e4;
|
|
80423
80524
|
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";
|
|
@@ -80427,14 +80528,14 @@ function rawEventSampleRetentionCutoff(referenceAt) {
|
|
|
80427
80528
|
return new Date(referenceMs - RAW_EVENT_SAMPLE_RETENTION_MS).toISOString();
|
|
80428
80529
|
}
|
|
80429
80530
|
function deleteExpiredRawEventSamples(db, cutoff, sourceId) {
|
|
80430
|
-
const sourceScope = sourceId === void 0 ? void 0 :
|
|
80431
|
-
db.delete(rawEventSamples).where(
|
|
80432
|
-
db.delete(rawEventSamples).where(
|
|
80531
|
+
const sourceScope = sourceId === void 0 ? void 0 : eq67(rawEventSamples.sourceId, sourceId);
|
|
80532
|
+
db.delete(rawEventSamples).where(and58(sourceScope, sql26`julianday(${rawEventSamples.ts}) IS NULL`)).run();
|
|
80533
|
+
db.delete(rawEventSamples).where(and58(
|
|
80433
80534
|
sourceScope,
|
|
80434
80535
|
sql26`${rawEventSamples.ts} GLOB ${CANONICAL_UTC_TIMESTAMP_GLOB}`,
|
|
80435
80536
|
lt11(rawEventSamples.ts, cutoff)
|
|
80436
80537
|
)).run();
|
|
80437
|
-
db.delete(rawEventSamples).where(
|
|
80538
|
+
db.delete(rawEventSamples).where(and58(
|
|
80438
80539
|
sourceScope,
|
|
80439
80540
|
sql26`${rawEventSamples.ts} NOT GLOB ${CANONICAL_UTC_TIMESTAMP_GLOB}`,
|
|
80440
80541
|
sql26`julianday(${rawEventSamples.ts}) < julianday(${cutoff})`
|
|
@@ -80483,16 +80584,16 @@ function writeTrafficEventBatch(opts) {
|
|
|
80483
80584
|
let aiReferralHits = 0;
|
|
80484
80585
|
let unknownHits = 0;
|
|
80485
80586
|
opts.db.transaction((tx) => {
|
|
80486
|
-
tx.update(trafficSources).set({ updatedAt: sql27`${trafficSources.updatedAt}` }).where(
|
|
80487
|
-
const source = tx.select().from(trafficSources).where(
|
|
80587
|
+
tx.update(trafficSources).set({ updatedAt: sql27`${trafficSources.updatedAt}` }).where(eq68(trafficSources.id, opts.sourceId)).run();
|
|
80588
|
+
const source = tx.select().from(trafficSources).where(eq68(trafficSources.id, opts.sourceId)).get();
|
|
80488
80589
|
opts.validateSource(source);
|
|
80489
80590
|
const rawSampleCutoff = enforceRawEventSampleRetention(
|
|
80490
80591
|
tx,
|
|
80491
80592
|
opts.sourceId,
|
|
80492
80593
|
opts.receivedAt
|
|
80493
80594
|
);
|
|
80494
|
-
tx.delete(trafficEventReceipts).where(
|
|
80495
|
-
|
|
80595
|
+
tx.delete(trafficEventReceipts).where(and59(
|
|
80596
|
+
eq68(trafficEventReceipts.sourceId, opts.sourceId),
|
|
80496
80597
|
lte15(trafficEventReceipts.expiresAt, opts.receivedAt)
|
|
80497
80598
|
)).run();
|
|
80498
80599
|
const claimedEvents = [];
|
|
@@ -80691,8 +80792,8 @@ function writeTrafficEventBatch(opts) {
|
|
|
80691
80792
|
if (!hour || hourlySampleLimit === 0) continue;
|
|
80692
80793
|
let remaining = remainingSamplesByHour.get(hour.start);
|
|
80693
80794
|
if (remaining === void 0) {
|
|
80694
|
-
const existing = tx.select({ total: sql27`COUNT(*)` }).from(rawEventSamples).where(
|
|
80695
|
-
|
|
80795
|
+
const existing = tx.select({ total: sql27`COUNT(*)` }).from(rawEventSamples).where(and59(
|
|
80796
|
+
eq68(rawEventSamples.sourceId, opts.sourceId),
|
|
80696
80797
|
gte15(rawEventSamples.ts, hour.start),
|
|
80697
80798
|
lt12(rawEventSamples.ts, hour.end)
|
|
80698
80799
|
)).get();
|
|
@@ -80733,7 +80834,7 @@ function writeTrafficEventBatch(opts) {
|
|
|
80733
80834
|
remainingSamplesByHour.set(hour.start, remaining - 1);
|
|
80734
80835
|
}
|
|
80735
80836
|
}
|
|
80736
|
-
tx.update(trafficSources).set(opts.sourceUpdate).where(
|
|
80837
|
+
tx.update(trafficSources).set(opts.sourceUpdate).where(eq68(trafficSources.id, opts.sourceId)).run();
|
|
80737
80838
|
});
|
|
80738
80839
|
return {
|
|
80739
80840
|
acceptedEvents,
|
|
@@ -80751,7 +80852,7 @@ function writeTrafficEventBatch(opts) {
|
|
|
80751
80852
|
}
|
|
80752
80853
|
|
|
80753
80854
|
// ../api-routes/src/traffic-sync-lease.ts
|
|
80754
|
-
import { and as
|
|
80855
|
+
import { and as and60, eq as eq69, isNull as isNull13, lte as lte16, or as or18 } from "drizzle-orm";
|
|
80755
80856
|
function leaseExpiresAt(now, ttlMs) {
|
|
80756
80857
|
const nowMs = Date.parse(now);
|
|
80757
80858
|
if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
|
|
@@ -80764,12 +80865,12 @@ function tryClaimTrafficSyncLease(input) {
|
|
|
80764
80865
|
const changed = tx.update(trafficSources).set({
|
|
80765
80866
|
syncLeaseOwner: input.owner,
|
|
80766
80867
|
syncLeaseExpiresAt: expiresAt
|
|
80767
|
-
}).where(
|
|
80768
|
-
|
|
80769
|
-
|
|
80868
|
+
}).where(and60(
|
|
80869
|
+
eq69(trafficSources.id, input.sourceId),
|
|
80870
|
+
or18(
|
|
80770
80871
|
isNull13(trafficSources.syncLeaseOwner),
|
|
80771
80872
|
lte16(trafficSources.syncLeaseExpiresAt, input.now),
|
|
80772
|
-
|
|
80873
|
+
eq69(trafficSources.syncLeaseOwner, input.owner)
|
|
80773
80874
|
)
|
|
80774
80875
|
)).run();
|
|
80775
80876
|
return changed.changes === 1;
|
|
@@ -80780,9 +80881,9 @@ function releaseTrafficSyncLease(input) {
|
|
|
80780
80881
|
const changed = tx.update(trafficSources).set({
|
|
80781
80882
|
syncLeaseOwner: null,
|
|
80782
80883
|
syncLeaseExpiresAt: null
|
|
80783
|
-
}).where(
|
|
80784
|
-
|
|
80785
|
-
|
|
80884
|
+
}).where(and60(
|
|
80885
|
+
eq69(trafficSources.id, input.sourceId),
|
|
80886
|
+
eq69(trafficSources.syncLeaseOwner, input.owner)
|
|
80786
80887
|
)).run();
|
|
80787
80888
|
return changed.changes === 1;
|
|
80788
80889
|
}, { behavior: "immediate" });
|
|
@@ -80863,7 +80964,7 @@ function cloudflareIngestRateLimitKey(request, store, db) {
|
|
|
80863
80964
|
const authenticated = authenticateCloudflareIngest(request, store);
|
|
80864
80965
|
const params = request.params;
|
|
80865
80966
|
if (authenticated && typeof params.name === "string" && authenticated.credential.projectName === params.name) {
|
|
80866
|
-
const source = db.select().from(trafficSources).where(
|
|
80967
|
+
const source = db.select().from(trafficSources).where(eq70(trafficSources.id, authenticated.credential.sourceId)).get();
|
|
80867
80968
|
if (source && source.sourceType === TrafficSourceTypes.cloudflare && source.status === TrafficSourceStatuses.connected && parseDirectPushCloudflareSourceConfig(source.configJson) && timingSafeEqualHex(authenticated.bearerHash, source.ingestTokenHash)) {
|
|
80868
80969
|
return `cloudflare-source:${authenticated.credential.sourceId}`;
|
|
80869
80970
|
}
|
|
@@ -81106,7 +81207,7 @@ async function defaultResolveAccessToken(record) {
|
|
|
81106
81207
|
);
|
|
81107
81208
|
}
|
|
81108
81209
|
function hasConnectedTrafficSourceSibling(db, projectId, sourceId) {
|
|
81109
|
-
return db.select().from(trafficSources).where(
|
|
81210
|
+
return db.select().from(trafficSources).where(eq70(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
|
|
81110
81211
|
}
|
|
81111
81212
|
function isAuthoritativeTrafficSource(db, source) {
|
|
81112
81213
|
return (source.status === TrafficSourceStatuses.connected || source.status === TrafficSourceStatuses.error) && !hasConnectedTrafficSourceSibling(db, source.projectId, source.id);
|
|
@@ -81130,13 +81231,13 @@ function wordpressBaseUrlIdentity(value) {
|
|
|
81130
81231
|
}
|
|
81131
81232
|
}
|
|
81132
81233
|
function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing = true) {
|
|
81133
|
-
const schedule = tx.select().from(schedules).where(
|
|
81134
|
-
|
|
81135
|
-
|
|
81234
|
+
const schedule = tx.select().from(schedules).where(and61(
|
|
81235
|
+
eq70(schedules.projectId, projectId),
|
|
81236
|
+
eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
81136
81237
|
)).get();
|
|
81137
81238
|
if (schedule) {
|
|
81138
81239
|
if (schedule.sourceId === sourceId) return { changed: false, created: false };
|
|
81139
|
-
tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(
|
|
81240
|
+
tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq70(schedules.id, schedule.id)).run();
|
|
81140
81241
|
return { changed: true, created: false };
|
|
81141
81242
|
}
|
|
81142
81243
|
if (!createIfMissing) return { changed: false, created: false };
|
|
@@ -81187,10 +81288,10 @@ async function runBackfillTask(options) {
|
|
|
81187
81288
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
81188
81289
|
try {
|
|
81189
81290
|
app.db.transaction((tx) => {
|
|
81190
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
81191
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
81291
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
|
|
81292
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
81192
81293
|
if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
|
|
81193
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
81294
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
81194
81295
|
}
|
|
81195
81296
|
});
|
|
81196
81297
|
} catch {
|
|
@@ -81217,7 +81318,7 @@ async function runBackfillTask(options) {
|
|
|
81217
81318
|
let emptyOutcome;
|
|
81218
81319
|
try {
|
|
81219
81320
|
const completed = app.db.transaction((tx) => {
|
|
81220
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
81321
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
81221
81322
|
const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2);
|
|
81222
81323
|
if (stillAuthoritative) {
|
|
81223
81324
|
enforceRawEventSampleRetention(tx, sourceRow2.id, finishedAt2);
|
|
@@ -81226,7 +81327,7 @@ async function runBackfillTask(options) {
|
|
|
81226
81327
|
status: RunStatuses.failed,
|
|
81227
81328
|
error: "Traffic source was deactivated or reconfigured during backfill",
|
|
81228
81329
|
finishedAt: finishedAt2
|
|
81229
|
-
}).where(
|
|
81330
|
+
}).where(eq70(runs.id, runId)).run();
|
|
81230
81331
|
return Boolean(stillAuthoritative);
|
|
81231
81332
|
});
|
|
81232
81333
|
emptyOutcome = completed ? { status: OutcomeStatuses.skipped, reasonCode: OutcomeReasonCodes.NO_DATA } : { status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_CONNECTED };
|
|
@@ -81252,13 +81353,13 @@ async function runBackfillTask(options) {
|
|
|
81252
81353
|
const nextLastSyncedAt = Math.max(currentLastSyncedMs, windowEnd.getTime()) === windowEnd.getTime() ? windowEndIso : sourceRow2.lastSyncedAt;
|
|
81253
81354
|
try {
|
|
81254
81355
|
const commitOutcome = app.db.transaction((tx) => {
|
|
81255
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
81356
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
81256
81357
|
if (latestSource?.sourceType === TrafficSourceTypes.wordpress) {
|
|
81257
81358
|
tx.update(runs).set({
|
|
81258
81359
|
status: RunStatuses.failed,
|
|
81259
81360
|
error: "Generic WordPress replace backfill is unavailable because retained coverage is unproven.",
|
|
81260
81361
|
finishedAt
|
|
81261
|
-
}).where(
|
|
81362
|
+
}).where(eq70(runs.id, runId)).run();
|
|
81262
81363
|
return "wordpress-backfill-unsupported";
|
|
81263
81364
|
}
|
|
81264
81365
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
|
|
@@ -81266,7 +81367,7 @@ async function runBackfillTask(options) {
|
|
|
81266
81367
|
status: RunStatuses.failed,
|
|
81267
81368
|
error: "Traffic source was deactivated or reconfigured during backfill",
|
|
81268
81369
|
finishedAt
|
|
81269
|
-
}).where(
|
|
81370
|
+
}).where(eq70(runs.id, runId)).run();
|
|
81270
81371
|
return "source-inactive";
|
|
81271
81372
|
}
|
|
81272
81373
|
const rawSampleCutoff = enforceRawEventSampleRetention(
|
|
@@ -81275,29 +81376,29 @@ async function runBackfillTask(options) {
|
|
|
81275
81376
|
finishedAt
|
|
81276
81377
|
);
|
|
81277
81378
|
tx.delete(crawlerEventsHourly).where(
|
|
81278
|
-
|
|
81279
|
-
|
|
81379
|
+
and61(
|
|
81380
|
+
eq70(crawlerEventsHourly.sourceId, sourceRow2.id),
|
|
81280
81381
|
gte16(crawlerEventsHourly.tsHour, windowStartIso),
|
|
81281
81382
|
lte17(crawlerEventsHourly.tsHour, windowEndIso)
|
|
81282
81383
|
)
|
|
81283
81384
|
).run();
|
|
81284
81385
|
tx.delete(aiUserFetchEventsHourly).where(
|
|
81285
|
-
|
|
81286
|
-
|
|
81386
|
+
and61(
|
|
81387
|
+
eq70(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
|
|
81287
81388
|
gte16(aiUserFetchEventsHourly.tsHour, windowStartIso),
|
|
81288
81389
|
lte17(aiUserFetchEventsHourly.tsHour, windowEndIso)
|
|
81289
81390
|
)
|
|
81290
81391
|
).run();
|
|
81291
81392
|
tx.delete(aiReferralEventsHourly).where(
|
|
81292
|
-
|
|
81293
|
-
|
|
81393
|
+
and61(
|
|
81394
|
+
eq70(aiReferralEventsHourly.sourceId, sourceRow2.id),
|
|
81294
81395
|
gte16(aiReferralEventsHourly.tsHour, windowStartIso),
|
|
81295
81396
|
lte17(aiReferralEventsHourly.tsHour, windowEndIso)
|
|
81296
81397
|
)
|
|
81297
81398
|
).run();
|
|
81298
81399
|
tx.delete(rawEventSamples).where(
|
|
81299
|
-
|
|
81300
|
-
|
|
81400
|
+
and61(
|
|
81401
|
+
eq70(rawEventSamples.sourceId, sourceRow2.id),
|
|
81301
81402
|
gte16(rawEventSamples.ts, windowStartIso),
|
|
81302
81403
|
lte17(rawEventSamples.ts, windowEndIso)
|
|
81303
81404
|
)
|
|
@@ -81489,8 +81590,8 @@ async function runBackfillTask(options) {
|
|
|
81489
81590
|
lastEventIds: newRingBuffer,
|
|
81490
81591
|
...skipRecovered ? { skippedThroughAt: null } : {},
|
|
81491
81592
|
updatedAt: finishedAt
|
|
81492
|
-
}).where(
|
|
81493
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
81593
|
+
}).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
81594
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq70(runs.id, runId)).run();
|
|
81494
81595
|
return "committed";
|
|
81495
81596
|
});
|
|
81496
81597
|
reportBackfill(commitOutcome === "committed" ? {
|
|
@@ -81652,7 +81753,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81652
81753
|
};
|
|
81653
81754
|
const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
|
|
81654
81755
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
81655
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
81756
|
+
const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
|
|
81656
81757
|
const sourceId = activeSource?.id ?? crypto48.randomUUID();
|
|
81657
81758
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
81658
81759
|
if (activeSource) {
|
|
@@ -81662,7 +81763,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81662
81763
|
lastError: null,
|
|
81663
81764
|
configJson: config,
|
|
81664
81765
|
updatedAt: now
|
|
81665
|
-
}).where(
|
|
81766
|
+
}).where(eq70(trafficSources.id, sourceId)).run();
|
|
81666
81767
|
} else {
|
|
81667
81768
|
tx.insert(trafficSources).values({
|
|
81668
81769
|
id: sourceId,
|
|
@@ -81688,7 +81789,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81688
81789
|
entityId: sourceId
|
|
81689
81790
|
});
|
|
81690
81791
|
return {
|
|
81691
|
-
sourceRow: tx.select().from(trafficSources).where(
|
|
81792
|
+
sourceRow: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get(),
|
|
81692
81793
|
scheduleChanged: scheduleBinding.changed
|
|
81693
81794
|
};
|
|
81694
81795
|
}, { behavior: "immediate" });
|
|
@@ -81743,7 +81844,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81743
81844
|
const config = { baseUrl, username };
|
|
81744
81845
|
const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
|
|
81745
81846
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
81746
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
81847
|
+
const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
|
|
81747
81848
|
const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
|
|
81748
81849
|
const sourceId = endpointChanged ? crypto48.randomUUID() : activeSource?.id ?? crypto48.randomUUID();
|
|
81749
81850
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
@@ -81754,14 +81855,14 @@ async function trafficRoutes(app, opts) {
|
|
|
81754
81855
|
lastError: null,
|
|
81755
81856
|
configJson: config,
|
|
81756
81857
|
updatedAt: now
|
|
81757
|
-
}).where(
|
|
81858
|
+
}).where(eq70(trafficSources.id, sourceId)).run();
|
|
81758
81859
|
} else {
|
|
81759
81860
|
if (activeSource) {
|
|
81760
81861
|
tx.update(trafficSources).set({
|
|
81761
81862
|
status: TrafficSourceStatuses.archived,
|
|
81762
81863
|
archivedAt: now,
|
|
81763
81864
|
updatedAt: now
|
|
81764
|
-
}).where(
|
|
81865
|
+
}).where(eq70(trafficSources.id, activeSource.id)).run();
|
|
81765
81866
|
}
|
|
81766
81867
|
tx.insert(trafficSources).values({
|
|
81767
81868
|
id: sourceId,
|
|
@@ -81787,7 +81888,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81787
81888
|
entityId: sourceId
|
|
81788
81889
|
});
|
|
81789
81890
|
return {
|
|
81790
|
-
sourceRow: tx.select().from(trafficSources).where(
|
|
81891
|
+
sourceRow: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get(),
|
|
81791
81892
|
scheduleChanged: scheduleBinding.changed
|
|
81792
81893
|
};
|
|
81793
81894
|
}, { behavior: "immediate" });
|
|
@@ -81844,7 +81945,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81844
81945
|
const config = { projectId, teamId, environment };
|
|
81845
81946
|
const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
|
|
81846
81947
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
81847
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
81948
|
+
const activeSource = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
|
|
81848
81949
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
81849
81950
|
let row;
|
|
81850
81951
|
if (activeSource) {
|
|
@@ -81854,8 +81955,8 @@ async function trafficRoutes(app, opts) {
|
|
|
81854
81955
|
lastError: null,
|
|
81855
81956
|
configJson: config,
|
|
81856
81957
|
updatedAt: now
|
|
81857
|
-
}).where(
|
|
81858
|
-
row = tx.select().from(trafficSources).where(
|
|
81958
|
+
}).where(eq70(trafficSources.id, activeSource.id)).run();
|
|
81959
|
+
row = tx.select().from(trafficSources).where(eq70(trafficSources.id, activeSource.id)).get();
|
|
81859
81960
|
} else {
|
|
81860
81961
|
const newId = crypto48.randomUUID();
|
|
81861
81962
|
tx.insert(trafficSources).values({
|
|
@@ -81880,7 +81981,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81880
81981
|
createdAt: now,
|
|
81881
81982
|
updatedAt: now
|
|
81882
81983
|
}).run();
|
|
81883
|
-
row = tx.select().from(trafficSources).where(
|
|
81984
|
+
row = tx.select().from(trafficSources).where(eq70(trafficSources.id, newId)).get();
|
|
81884
81985
|
}
|
|
81885
81986
|
const scheduleBinding = sourceStatus === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, row.id, now) : { changed: false, created: false };
|
|
81886
81987
|
writeAuditLog(tx, {
|
|
@@ -81924,7 +82025,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81924
82025
|
const { displayName: displayName2, zoneId: zoneId2, accountId: accountId2, queueId, queueName, retentionSeconds, apiToken } = parsed.data;
|
|
81925
82026
|
const workerRouteHost2 = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
|
|
81926
82027
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
81927
|
-
const cloudflareSources = app.db.select().from(trafficSources).where(
|
|
82028
|
+
const cloudflareSources = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
|
|
81928
82029
|
const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
|
|
81929
82030
|
if (sameModeSource) attempt.update({ action: "reauth" });
|
|
81930
82031
|
const sourceId2 = sameModeSource?.id ?? crypto48.randomUUID();
|
|
@@ -81975,7 +82076,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81975
82076
|
let scheduleChanged = false;
|
|
81976
82077
|
try {
|
|
81977
82078
|
const result = app.db.transaction((tx) => {
|
|
81978
|
-
const currentSameModeSource = tx.select().from(trafficSources).where(
|
|
82079
|
+
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);
|
|
81979
82080
|
if ((currentSameModeSource?.id ?? null) !== (sameModeSource?.id ?? null)) {
|
|
81980
82081
|
throw operationInProgress("Cloudflare Queue source changed during connect; retry");
|
|
81981
82082
|
}
|
|
@@ -81993,7 +82094,7 @@ async function trafficRoutes(app, opts) {
|
|
|
81993
82094
|
queueBacklogObservedAt: null
|
|
81994
82095
|
} : {},
|
|
81995
82096
|
updatedAt: now2
|
|
81996
|
-
}).where(
|
|
82097
|
+
}).where(eq70(trafficSources.id, sourceId2)).run();
|
|
81997
82098
|
} else {
|
|
81998
82099
|
tx.insert(trafficSources).values({
|
|
81999
82100
|
id: sourceId2,
|
|
@@ -82013,7 +82114,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82013
82114
|
}
|
|
82014
82115
|
const changed = status === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, sourceId2, now2).changed : false;
|
|
82015
82116
|
return {
|
|
82016
|
-
row: tx.select().from(trafficSources).where(
|
|
82117
|
+
row: tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId2)).get(),
|
|
82017
82118
|
scheduleChanged: changed
|
|
82018
82119
|
};
|
|
82019
82120
|
}, { behavior: "immediate" });
|
|
@@ -82061,7 +82162,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82061
82162
|
const { deliveryMode, displayName, zoneId, accountId } = parsed.data;
|
|
82062
82163
|
const workerRouteHost = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
|
|
82063
82164
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
82064
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
82165
|
+
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);
|
|
82065
82166
|
if (activeSource) attempt.update({ action: "reauth" });
|
|
82066
82167
|
const sourceId = activeSource?.id ?? crypto48.randomUUID();
|
|
82067
82168
|
const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
|
|
@@ -82126,7 +82227,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82126
82227
|
let scheduleRemoved = false;
|
|
82127
82228
|
try {
|
|
82128
82229
|
const result = app.db.transaction((tx) => {
|
|
82129
|
-
const currentActiveSource = tx.select().from(trafficSources).where(
|
|
82230
|
+
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);
|
|
82130
82231
|
if ((currentActiveSource?.id ?? null) !== (activeSource?.id ?? null)) {
|
|
82131
82232
|
throw operationInProgress("Cloudflare direct-push source changed during connect; retry");
|
|
82132
82233
|
}
|
|
@@ -82140,8 +82241,8 @@ async function trafficRoutes(app, opts) {
|
|
|
82140
82241
|
configJson: config,
|
|
82141
82242
|
ingestTokenHash,
|
|
82142
82243
|
updatedAt: now
|
|
82143
|
-
}).where(
|
|
82144
|
-
row = tx.select().from(trafficSources).where(
|
|
82244
|
+
}).where(eq70(trafficSources.id, currentActiveSource.id)).run();
|
|
82245
|
+
row = tx.select().from(trafficSources).where(eq70(trafficSources.id, currentActiveSource.id)).get();
|
|
82145
82246
|
} else {
|
|
82146
82247
|
tx.insert(trafficSources).values({
|
|
82147
82248
|
id: sourceId,
|
|
@@ -82161,16 +82262,16 @@ async function trafficRoutes(app, opts) {
|
|
|
82161
82262
|
createdAt: now,
|
|
82162
82263
|
updatedAt: now
|
|
82163
82264
|
}).run();
|
|
82164
|
-
row = tx.select().from(trafficSources).where(
|
|
82265
|
+
row = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get();
|
|
82165
82266
|
}
|
|
82166
82267
|
let removed = false;
|
|
82167
82268
|
if (row.status === TrafficSourceStatuses.connected) {
|
|
82168
|
-
const schedule = tx.select().from(schedules).where(
|
|
82169
|
-
|
|
82170
|
-
|
|
82269
|
+
const schedule = tx.select().from(schedules).where(and61(
|
|
82270
|
+
eq70(schedules.projectId, project.id),
|
|
82271
|
+
eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
82171
82272
|
)).get();
|
|
82172
82273
|
if (schedule) {
|
|
82173
|
-
tx.delete(schedules).where(
|
|
82274
|
+
tx.delete(schedules).where(eq70(schedules.id, schedule.id)).run();
|
|
82174
82275
|
removed = true;
|
|
82175
82276
|
}
|
|
82176
82277
|
}
|
|
@@ -82224,47 +82325,47 @@ async function trafficRoutes(app, opts) {
|
|
|
82224
82325
|
};
|
|
82225
82326
|
}));
|
|
82226
82327
|
app.post("/projects/:name/traffic/sources/:id/activate", connectionRoute(app, (request) => {
|
|
82227
|
-
const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(
|
|
82328
|
+
const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
|
|
82228
82329
|
const integration = source ? TRAFFIC_SOURCE_INTEGRATIONS[source.sourceType] : void 0;
|
|
82229
82330
|
return integration ? { integration, action: "select" } : void 0;
|
|
82230
82331
|
}, async (request) => {
|
|
82231
82332
|
const project = resolveProject(app.db, request.params.name);
|
|
82232
82333
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
82233
82334
|
const result = app.db.transaction((tx) => {
|
|
82234
|
-
const target = tx.select().from(trafficSources).where(
|
|
82335
|
+
const target = tx.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
|
|
82235
82336
|
if (!target || target.projectId !== project.id || target.status === TrafficSourceStatuses.archived) {
|
|
82236
82337
|
throw notFound("Traffic source", request.params.id);
|
|
82237
82338
|
}
|
|
82238
82339
|
const { usesPullSchedule } = validateTrafficSourceCredential(target, project.name);
|
|
82239
|
-
const siblings = tx.select().from(trafficSources).where(
|
|
82340
|
+
const siblings = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all();
|
|
82240
82341
|
for (const sibling of siblings) {
|
|
82241
82342
|
if (sibling.id === target.id || sibling.status === TrafficSourceStatuses.archived) continue;
|
|
82242
82343
|
tx.update(trafficSources).set({
|
|
82243
82344
|
status: TrafficSourceStatuses.paused,
|
|
82244
82345
|
updatedAt: now
|
|
82245
|
-
}).where(
|
|
82346
|
+
}).where(eq70(trafficSources.id, sibling.id)).run();
|
|
82246
82347
|
}
|
|
82247
82348
|
tx.update(trafficSources).set({
|
|
82248
82349
|
status: TrafficSourceStatuses.connected,
|
|
82249
82350
|
lastError: null,
|
|
82250
82351
|
archivedAt: null,
|
|
82251
82352
|
updatedAt: now
|
|
82252
|
-
}).where(
|
|
82353
|
+
}).where(eq70(trafficSources.id, target.id)).run();
|
|
82253
82354
|
let scheduleAction = null;
|
|
82254
82355
|
if (usesPullSchedule) {
|
|
82255
82356
|
bindTrafficSyncSchedule(tx, project.id, target.id, now);
|
|
82256
82357
|
scheduleAction = "upsert";
|
|
82257
82358
|
} else {
|
|
82258
|
-
const schedule = tx.select().from(schedules).where(
|
|
82259
|
-
|
|
82260
|
-
|
|
82359
|
+
const schedule = tx.select().from(schedules).where(and61(
|
|
82360
|
+
eq70(schedules.projectId, project.id),
|
|
82361
|
+
eq70(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
82261
82362
|
)).get();
|
|
82262
82363
|
if (schedule) {
|
|
82263
|
-
tx.delete(schedules).where(
|
|
82364
|
+
tx.delete(schedules).where(eq70(schedules.id, schedule.id)).run();
|
|
82264
82365
|
scheduleAction = "delete";
|
|
82265
82366
|
}
|
|
82266
82367
|
}
|
|
82267
|
-
const source = tx.select().from(trafficSources).where(
|
|
82368
|
+
const source = tx.select().from(trafficSources).where(eq70(trafficSources.id, target.id)).get();
|
|
82268
82369
|
writeAuditLog(tx, {
|
|
82269
82370
|
projectId: project.id,
|
|
82270
82371
|
actor: "api",
|
|
@@ -82332,7 +82433,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82332
82433
|
}
|
|
82333
82434
|
const { bearerHash, credential } = authenticated;
|
|
82334
82435
|
const sourceId = credential.sourceId;
|
|
82335
|
-
const sourceRow2 = app.db.select().from(trafficSources).where(
|
|
82436
|
+
const sourceRow2 = app.db.select().from(trafficSources).where(eq70(trafficSources.id, sourceId)).get();
|
|
82336
82437
|
if (!sourceRow2 || sourceRow2.sourceType !== TrafficSourceTypes.cloudflare || sourceRow2.status !== TrafficSourceStatuses.connected || !parseDirectPushCloudflareSourceConfig(sourceRow2.configJson) || !timingSafeEqualHex(bearerHash, sourceRow2.ingestTokenHash)) {
|
|
82337
82438
|
throw authRequired();
|
|
82338
82439
|
}
|
|
@@ -82406,7 +82507,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82406
82507
|
});
|
|
82407
82508
|
app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
|
|
82408
82509
|
const project = resolveProject(app.db, request.params.name);
|
|
82409
|
-
const initialSourceRow = app.db.select().from(trafficSources).where(
|
|
82510
|
+
const initialSourceRow = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
|
|
82410
82511
|
if (!initialSourceRow || initialSourceRow.projectId !== project.id) {
|
|
82411
82512
|
throw notFound("Traffic source", request.params.id);
|
|
82412
82513
|
}
|
|
@@ -82561,7 +82662,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82561
82662
|
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
82562
82663
|
ttlMs: CLOUDFLARE_QUEUE_SYNC_LEASE_TTL_MS
|
|
82563
82664
|
})) throw new Error("Queue sync lease was lost before acknowledgement");
|
|
82564
|
-
const latestSource = app.db.select().from(trafficSources).where(
|
|
82665
|
+
const latestSource = app.db.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
82565
82666
|
if (!latestSource || latestSource.projectId !== project.id || latestSource.status !== TrafficSourceStatuses.connected || latestSource.syncLeaseOwner !== leaseOwner || !isDeepStrictEqual2(latestSource.configJson, sourceRow2.configJson)) {
|
|
82566
82667
|
throw validationError("Cloudflare Queue source was reconfigured before acknowledgement");
|
|
82567
82668
|
}
|
|
@@ -82586,7 +82687,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82586
82687
|
const pulledEnvelopeCount = pulled.messages.length + pulled.skippedUnleasedMessageCount;
|
|
82587
82688
|
if (pulledEnvelopeCount < CLOUDFLARE_QUEUE_BATCH_SIZE || remainingBacklogCount === 0) break;
|
|
82588
82689
|
}
|
|
82589
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: committedAt }).where(
|
|
82690
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: committedAt }).where(eq70(runs.id, runId)).run();
|
|
82590
82691
|
writeAuditLog(app.db, {
|
|
82591
82692
|
projectId: project.id,
|
|
82592
82693
|
actor: "api",
|
|
@@ -82647,13 +82748,13 @@ async function trafficRoutes(app, opts) {
|
|
|
82647
82748
|
} catch {
|
|
82648
82749
|
}
|
|
82649
82750
|
app.db.transaction((tx) => {
|
|
82650
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: safeError, finishedAt: failedAt }).where(
|
|
82651
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
82751
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: safeError, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
|
|
82752
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
82652
82753
|
if (latestSource && latestSource.status === TrafficSourceStatuses.connected && latestSource.syncLeaseOwner === leaseOwner && isDeepStrictEqual2(latestSource.configJson, sourceRow2.configJson)) {
|
|
82653
|
-
tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(
|
|
82654
|
-
|
|
82655
|
-
|
|
82656
|
-
|
|
82754
|
+
tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and61(
|
|
82755
|
+
eq70(trafficSources.id, sourceRow2.id),
|
|
82756
|
+
eq70(trafficSources.status, TrafficSourceStatuses.connected),
|
|
82757
|
+
eq70(trafficSources.syncLeaseOwner, leaseOwner)
|
|
82657
82758
|
)).run();
|
|
82658
82759
|
}
|
|
82659
82760
|
});
|
|
@@ -82672,7 +82773,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82672
82773
|
`Sync for source type "${sourceRow2.sourceType}" is not implemented yet \u2014 only cloud-run, wordpress, and vercel are supported in v1.`
|
|
82673
82774
|
);
|
|
82674
82775
|
}
|
|
82675
|
-
const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(
|
|
82776
|
+
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);
|
|
82676
82777
|
if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
|
|
82677
82778
|
throw validationError("Traffic source must be active before it can sync");
|
|
82678
82779
|
}
|
|
@@ -82705,10 +82806,10 @@ async function trafficRoutes(app, opts) {
|
|
|
82705
82806
|
const markFailed = (msg, errorCode2) => {
|
|
82706
82807
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
82707
82808
|
app.db.transaction((tx) => {
|
|
82708
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
82709
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
82809
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq70(runs.id, runId)).run();
|
|
82810
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
82710
82811
|
if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && (wordpressLeaseOwner === void 0 || latestSource.syncLeaseOwner === wordpressLeaseOwner)) {
|
|
82711
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
82812
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
82712
82813
|
}
|
|
82713
82814
|
});
|
|
82714
82815
|
try {
|
|
@@ -82802,7 +82903,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82802
82903
|
}
|
|
82803
82904
|
const credential = credentialStore.getConnection(project.name);
|
|
82804
82905
|
if (!credential) {
|
|
82805
|
-
app.db.delete(runs).where(
|
|
82906
|
+
app.db.delete(runs).where(eq70(runs.id, runId)).run();
|
|
82806
82907
|
throw validationError(
|
|
82807
82908
|
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
82808
82909
|
);
|
|
@@ -82836,14 +82937,14 @@ async function trafficRoutes(app, opts) {
|
|
|
82836
82937
|
const windowEndIso2 = windowEnd.toISOString();
|
|
82837
82938
|
const reservedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
82838
82939
|
const reservedSource = app.db.transaction((tx) => {
|
|
82839
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
82940
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
82840
82941
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== wordpressLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
|
|
82841
82942
|
tx.update(trafficSources).set({
|
|
82842
82943
|
lastSyncedAt: windowStartIso2,
|
|
82843
82944
|
wordpressPendingUntil: windowEndIso2,
|
|
82844
82945
|
updatedAt: reservedAt
|
|
82845
|
-
}).where(
|
|
82846
|
-
return tx.select().from(trafficSources).where(
|
|
82946
|
+
}).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
82947
|
+
return tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
82847
82948
|
}, { behavior: "immediate" });
|
|
82848
82949
|
if (!reservedSource) {
|
|
82849
82950
|
const message = "Traffic source changed while reserving the WordPress sync window; retry the sync.";
|
|
@@ -82903,12 +83004,12 @@ async function trafficRoutes(app, opts) {
|
|
|
82903
83004
|
auditAction = "traffic.vercel.synced";
|
|
82904
83005
|
const credentialStore = opts.vercelTrafficCredentialStore;
|
|
82905
83006
|
if (!credentialStore) {
|
|
82906
|
-
app.db.delete(runs).where(
|
|
83007
|
+
app.db.delete(runs).where(eq70(runs.id, runId)).run();
|
|
82907
83008
|
throw validationError("Vercel traffic credential storage is not configured for this deployment");
|
|
82908
83009
|
}
|
|
82909
83010
|
const credential = credentialStore.getConnection(project.name);
|
|
82910
83011
|
if (!credential) {
|
|
82911
|
-
app.db.delete(runs).where(
|
|
83012
|
+
app.db.delete(runs).where(eq70(runs.id, runId)).run();
|
|
82912
83013
|
throw validationError(
|
|
82913
83014
|
`No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
|
|
82914
83015
|
);
|
|
@@ -82926,7 +83027,7 @@ async function trafficRoutes(app, opts) {
|
|
|
82926
83027
|
if (cappedStartMs > clampedStartMs) {
|
|
82927
83028
|
const previousSkip = sourceRow2.skippedThroughAt ? Date.parse(sourceRow2.skippedThroughAt) : Number.NaN;
|
|
82928
83029
|
const skippedThrough = Number.isFinite(previousSkip) ? new Date(Math.max(previousSkip, cappedStartMs)) : new Date(cappedStartMs);
|
|
82929
|
-
app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(
|
|
83030
|
+
app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
82930
83031
|
request.log.warn(
|
|
82931
83032
|
{
|
|
82932
83033
|
sourceId: sourceRow2.id,
|
|
@@ -83019,15 +83120,15 @@ async function trafficRoutes(app, opts) {
|
|
|
83019
83120
|
throw providerError(`WordPress sync failed: ${msg}`);
|
|
83020
83121
|
}
|
|
83021
83122
|
const commitOutcome = app.db.transaction((tx) => {
|
|
83022
|
-
const latestRow = tx.select().from(trafficSources).where(
|
|
83023
|
-
const latestHasConnectedSibling = tx.select().from(trafficSources).where(
|
|
83123
|
+
const latestRow = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
83124
|
+
const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
|
|
83024
83125
|
if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || wordpressLeaseOwner !== void 0 && latestRow.syncLeaseOwner !== wordpressLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
|
|
83025
83126
|
const abortedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
83026
83127
|
tx.update(runs).set({
|
|
83027
83128
|
status: RunStatuses.failed,
|
|
83028
83129
|
error: "Traffic source was deactivated or reconfigured during sync",
|
|
83029
83130
|
finishedAt: abortedAt
|
|
83030
|
-
}).where(
|
|
83131
|
+
}).where(eq70(runs.id, runId)).run();
|
|
83031
83132
|
return "source-inactive";
|
|
83032
83133
|
}
|
|
83033
83134
|
const previousIds = latestRow.lastEventIds ?? [];
|
|
@@ -83290,8 +83391,8 @@ async function trafficRoutes(app, opts) {
|
|
|
83290
83391
|
sourceUpdate.lastCursor = nextCursor ?? null;
|
|
83291
83392
|
sourceUpdate.wordpressPendingUntil = nextCursor ? windowEnd.toISOString() : null;
|
|
83292
83393
|
}
|
|
83293
|
-
tx.update(trafficSources).set(sourceUpdate).where(
|
|
83294
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
83394
|
+
tx.update(trafficSources).set(sourceUpdate).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
83395
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq70(runs.id, runId)).run();
|
|
83295
83396
|
return "committed";
|
|
83296
83397
|
}, { behavior: "immediate" });
|
|
83297
83398
|
if (commitOutcome === "source-inactive") {
|
|
@@ -83361,7 +83462,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83361
83462
|
});
|
|
83362
83463
|
app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => withFeatureOutcome(app, { feature: "server_traffic", operation: "backfill" }, async (recordOutcome) => {
|
|
83363
83464
|
const project = resolveProject(app.db, request.params.name);
|
|
83364
|
-
const sourceRow2 = app.db.select().from(trafficSources).where(
|
|
83465
|
+
const sourceRow2 = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
|
|
83365
83466
|
if (!sourceRow2 || sourceRow2.projectId !== project.id) {
|
|
83366
83467
|
throw notFound("Traffic source", request.params.id);
|
|
83367
83468
|
}
|
|
@@ -83474,7 +83575,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83474
83575
|
const startedAt = windowEnd.toISOString();
|
|
83475
83576
|
const runId = crypto48.randomUUID();
|
|
83476
83577
|
app.db.transaction((tx) => {
|
|
83477
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
83578
|
+
const latestSource = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
83478
83579
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
|
|
83479
83580
|
throw validationError("Traffic source must remain active and unchanged before it can backfill");
|
|
83480
83581
|
}
|
|
@@ -83517,8 +83618,8 @@ async function trafficRoutes(app, opts) {
|
|
|
83517
83618
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
83518
83619
|
hits: sql28`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
83519
83620
|
}).from(crawlerEventsHourly).where(
|
|
83520
|
-
|
|
83521
|
-
|
|
83621
|
+
and61(
|
|
83622
|
+
eq70(crawlerEventsHourly.sourceId, row.id),
|
|
83522
83623
|
gte16(crawlerEventsHourly.tsHour, since)
|
|
83523
83624
|
)
|
|
83524
83625
|
).groupBy(crawlerEventsHourly.pathNormalized).all();
|
|
@@ -83527,8 +83628,8 @@ async function trafficRoutes(app, opts) {
|
|
|
83527
83628
|
);
|
|
83528
83629
|
const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
|
|
83529
83630
|
const aiUserFetchTotals = app.db.select({ total: sql28`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
83530
|
-
|
|
83531
|
-
|
|
83631
|
+
and61(
|
|
83632
|
+
eq70(aiUserFetchEventsHourly.sourceId, row.id),
|
|
83532
83633
|
gte16(aiUserFetchEventsHourly.tsHour, since)
|
|
83533
83634
|
)
|
|
83534
83635
|
).get();
|
|
@@ -83537,22 +83638,22 @@ async function trafficRoutes(app, opts) {
|
|
|
83537
83638
|
nonhop: sql28`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
|
|
83538
83639
|
landed: sql28`COALESCE(SUM(CASE WHEN ${countableReferralCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`
|
|
83539
83640
|
}).from(aiReferralEventsHourly).where(
|
|
83540
|
-
|
|
83541
|
-
|
|
83641
|
+
and61(
|
|
83642
|
+
eq70(aiReferralEventsHourly.sourceId, row.id),
|
|
83542
83643
|
gte16(aiReferralEventsHourly.tsHour, since)
|
|
83543
83644
|
)
|
|
83544
83645
|
).get();
|
|
83545
83646
|
const sampleTotals = app.db.select({ total: sql28`COUNT(*)` }).from(rawEventSamples).where(
|
|
83546
|
-
|
|
83547
|
-
|
|
83647
|
+
and61(
|
|
83648
|
+
eq70(rawEventSamples.sourceId, row.id),
|
|
83548
83649
|
gte16(rawEventSamples.ts, since)
|
|
83549
83650
|
)
|
|
83550
83651
|
).get();
|
|
83551
83652
|
const latestRun = app.db.select().from(runs).where(
|
|
83552
|
-
|
|
83553
|
-
|
|
83554
|
-
|
|
83555
|
-
|
|
83653
|
+
and61(
|
|
83654
|
+
eq70(runs.projectId, projectId),
|
|
83655
|
+
eq70(runs.kind, RunKinds["traffic-sync"]),
|
|
83656
|
+
eq70(runs.sourceId, row.id)
|
|
83556
83657
|
)
|
|
83557
83658
|
).orderBy(desc35(runs.startedAt)).limit(1).get();
|
|
83558
83659
|
return {
|
|
@@ -83588,7 +83689,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83588
83689
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
83589
83690
|
let updatedRow;
|
|
83590
83691
|
app.db.transaction((tx) => {
|
|
83591
|
-
const sourceRow2 = tx.select().from(trafficSources).where(
|
|
83692
|
+
const sourceRow2 = tx.select().from(trafficSources).where(and61(eq70(trafficSources.projectId, project.id), eq70(trafficSources.id, request.params.id))).get();
|
|
83592
83693
|
if (!sourceRow2) {
|
|
83593
83694
|
throw notFound("traffic source", request.params.id);
|
|
83594
83695
|
}
|
|
@@ -83602,7 +83703,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83602
83703
|
`Traffic source "${sourceRow2.id}" is staged. Activate it explicitly instead of resetting it.`
|
|
83603
83704
|
);
|
|
83604
83705
|
}
|
|
83605
|
-
const hasConnectedSibling = tx.select().from(trafficSources).where(
|
|
83706
|
+
const hasConnectedSibling = tx.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
|
|
83606
83707
|
if (hasConnectedSibling) {
|
|
83607
83708
|
throw validationError(
|
|
83608
83709
|
`Traffic source "${sourceRow2.id}" is not authoritative. Activate it explicitly instead of resetting it.`
|
|
@@ -83619,7 +83720,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83619
83720
|
status: TrafficSourceStatuses.connected,
|
|
83620
83721
|
lastError: null,
|
|
83621
83722
|
updatedAt: now
|
|
83622
|
-
}).where(
|
|
83723
|
+
}).where(eq70(trafficSources.id, sourceRow2.id)).run();
|
|
83623
83724
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
83624
83725
|
projectId: project.id,
|
|
83625
83726
|
actor: "api",
|
|
@@ -83627,20 +83728,20 @@ async function trafficRoutes(app, opts) {
|
|
|
83627
83728
|
entityType: "traffic_source",
|
|
83628
83729
|
entityId: sourceRow2.id
|
|
83629
83730
|
}));
|
|
83630
|
-
updatedRow = tx.select().from(trafficSources).where(
|
|
83731
|
+
updatedRow = tx.select().from(trafficSources).where(eq70(trafficSources.id, sourceRow2.id)).get();
|
|
83631
83732
|
});
|
|
83632
83733
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
83633
83734
|
}));
|
|
83634
83735
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
83635
83736
|
const project = resolveProject(app.db, request.params.name);
|
|
83636
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
83737
|
+
const rows = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
|
|
83637
83738
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
|
|
83638
83739
|
const response = { sources };
|
|
83639
83740
|
return response;
|
|
83640
83741
|
});
|
|
83641
83742
|
app.get("/projects/:name/traffic/status", async (request) => {
|
|
83642
83743
|
const project = resolveProject(app.db, request.params.name);
|
|
83643
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
83744
|
+
const rows = app.db.select().from(trafficSources).where(eq70(trafficSources.projectId, project.id)).orderBy(desc35(trafficSources.createdAt)).all();
|
|
83644
83745
|
const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
|
|
83645
83746
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
|
|
83646
83747
|
const response = { sources };
|
|
@@ -83650,7 +83751,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83650
83751
|
"/projects/:name/traffic/sources/:id",
|
|
83651
83752
|
async (request) => {
|
|
83652
83753
|
const project = resolveProject(app.db, request.params.name);
|
|
83653
|
-
const row = app.db.select().from(trafficSources).where(
|
|
83754
|
+
const row = app.db.select().from(trafficSources).where(eq70(trafficSources.id, request.params.id)).get();
|
|
83654
83755
|
if (!row || row.projectId !== project.id) {
|
|
83655
83756
|
throw notFound("Traffic source", request.params.id);
|
|
83656
83757
|
}
|
|
@@ -83723,12 +83824,12 @@ async function trafficRoutes(app, opts) {
|
|
|
83723
83824
|
const seriesByBucket = /* @__PURE__ */ new Map();
|
|
83724
83825
|
if (kind === "all" || kind === TrafficEventKinds.crawler) {
|
|
83725
83826
|
const crawlerFilters = [
|
|
83726
|
-
|
|
83827
|
+
eq70(crawlerEventsHourly.projectId, project.id),
|
|
83727
83828
|
gte16(crawlerEventsHourly.tsHour, sinceIso),
|
|
83728
83829
|
lte17(crawlerEventsHourly.tsHour, untilIso)
|
|
83729
83830
|
];
|
|
83730
|
-
if (sourceIdParam) crawlerFilters.push(
|
|
83731
|
-
const crawlerWhere =
|
|
83831
|
+
if (sourceIdParam) crawlerFilters.push(eq70(crawlerEventsHourly.sourceId, sourceIdParam));
|
|
83832
|
+
const crawlerWhere = and61(...crawlerFilters);
|
|
83732
83833
|
const pathTotals = app.db.select({
|
|
83733
83834
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
83734
83835
|
hits: sql28`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
|
|
@@ -83798,12 +83899,12 @@ async function trafficRoutes(app, opts) {
|
|
|
83798
83899
|
}
|
|
83799
83900
|
if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
|
|
83800
83901
|
const userFetchFilters = [
|
|
83801
|
-
|
|
83902
|
+
eq70(aiUserFetchEventsHourly.projectId, project.id),
|
|
83802
83903
|
gte16(aiUserFetchEventsHourly.tsHour, sinceIso),
|
|
83803
83904
|
lte17(aiUserFetchEventsHourly.tsHour, untilIso)
|
|
83804
83905
|
];
|
|
83805
|
-
if (sourceIdParam) userFetchFilters.push(
|
|
83806
|
-
const userFetchWhere =
|
|
83906
|
+
if (sourceIdParam) userFetchFilters.push(eq70(aiUserFetchEventsHourly.sourceId, sourceIdParam));
|
|
83907
|
+
const userFetchWhere = and61(...userFetchFilters);
|
|
83807
83908
|
const total = app.db.select({
|
|
83808
83909
|
total: sql28`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
|
|
83809
83910
|
rows: sql28`COUNT(*)`
|
|
@@ -83837,12 +83938,12 @@ async function trafficRoutes(app, opts) {
|
|
|
83837
83938
|
}
|
|
83838
83939
|
if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
|
|
83839
83940
|
const aiFilters = [
|
|
83840
|
-
|
|
83941
|
+
eq70(aiReferralEventsHourly.projectId, project.id),
|
|
83841
83942
|
gte16(aiReferralEventsHourly.tsHour, sinceIso),
|
|
83842
83943
|
lte17(aiReferralEventsHourly.tsHour, untilIso)
|
|
83843
83944
|
];
|
|
83844
|
-
if (sourceIdParam) aiFilters.push(
|
|
83845
|
-
const aiWhere =
|
|
83945
|
+
if (sourceIdParam) aiFilters.push(eq70(aiReferralEventsHourly.sourceId, sourceIdParam));
|
|
83946
|
+
const aiWhere = and61(...aiFilters);
|
|
83846
83947
|
const total = app.db.select({
|
|
83847
83948
|
total: sql28`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
|
|
83848
83949
|
nonhop: sql28`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
|
|
@@ -83917,7 +84018,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83917
84018
|
windowEnd: untilIso,
|
|
83918
84019
|
series: (() => {
|
|
83919
84020
|
const points = completeTrafficSeries(since, until, granularity, seriesByBucket);
|
|
83920
|
-
const coverageScope = (col, srcCol) => sourceIdParam ?
|
|
84021
|
+
const coverageScope = (col, srcCol) => sourceIdParam ? and61(eq70(col, project.id), eq70(srcCol, sourceIdParam)) : eq70(col, project.id);
|
|
83921
84022
|
const firsts = [
|
|
83922
84023
|
app.db.select({ v: sql28`MIN(${crawlerEventsHourly.tsHour})` }).from(crawlerEventsHourly).where(coverageScope(crawlerEventsHourly.projectId, crawlerEventsHourly.sourceId)).get()?.v,
|
|
83923
84024
|
app.db.select({ v: sql28`MIN(${aiUserFetchEventsHourly.tsHour})` }).from(aiUserFetchEventsHourly).where(coverageScope(aiUserFetchEventsHourly.projectId, aiUserFetchEventsHourly.sourceId)).get()?.v,
|
|
@@ -83995,7 +84096,7 @@ async function trafficRoutes(app, opts) {
|
|
|
83995
84096
|
}
|
|
83996
84097
|
|
|
83997
84098
|
// ../api-routes/src/doctor/checks/report-sweeps.ts
|
|
83998
|
-
import { and as
|
|
84099
|
+
import { and as and62, asc as asc15, eq as eq71, gte as gte17, lte as lte18 } from "drizzle-orm";
|
|
83999
84100
|
|
|
84000
84101
|
// ../api-routes/src/visibility-attribution.ts
|
|
84001
84102
|
function buildQueryAttribution2(projectQueries) {
|
|
@@ -84029,11 +84130,11 @@ function inspectSweep(ctx, run) {
|
|
|
84029
84130
|
answerMentioned: querySnapshots.answerMentioned,
|
|
84030
84131
|
answerText: querySnapshots.answerText,
|
|
84031
84132
|
measurementExecutionId: querySnapshots.measurementExecutionId
|
|
84032
|
-
}).from(querySnapshots).where(
|
|
84133
|
+
}).from(querySnapshots).where(eq71(querySnapshots.runId, run.id)).all();
|
|
84033
84134
|
if (snapshots.length === 0) return { detail: { ...base, complete: false, reason: "no-answers" } };
|
|
84034
84135
|
try {
|
|
84035
84136
|
if (run.measurementPlanVersionId) {
|
|
84036
|
-
const version = ctx.db.select().from(measurementPlanVersions).where(
|
|
84137
|
+
const version = ctx.db.select().from(measurementPlanVersions).where(and62(eq71(measurementPlanVersions.projectId, ctx.project.id), eq71(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
|
|
84037
84138
|
if (!version) return { detail: { ...base, complete: false, reason: "missing-plan" } };
|
|
84038
84139
|
const plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
|
|
84039
84140
|
const manifest = measurementRunExpectedSlots(run, plan);
|
|
@@ -84065,10 +84166,10 @@ function inspectSweep(ctx, run) {
|
|
|
84065
84166
|
};
|
|
84066
84167
|
return unbound ? { detail } : { detail, coverage: slotCoverage(expected2, answered3) };
|
|
84067
84168
|
}
|
|
84068
|
-
const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(
|
|
84169
|
+
const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq71(simpleMeasurementDefinitions.runId, run.id)).get();
|
|
84069
84170
|
const frozen = stored ? simpleMeasurementDefinitionSchema.parse(stored.definition) : null;
|
|
84070
|
-
const project = ctx.db.select().from(projects).where(
|
|
84071
|
-
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(
|
|
84171
|
+
const project = ctx.db.select().from(projects).where(eq71(projects.id, ctx.project.id)).get();
|
|
84172
|
+
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();
|
|
84072
84173
|
const providers = frozen ? frozen.engines.map((engine) => engine.provider) : project.providers.length > 0 ? project.providers : [...new Set(snapshots.map((snapshot) => snapshot.provider))];
|
|
84073
84174
|
const attribution = buildQueryAttribution2(basket);
|
|
84074
84175
|
const expected = new Set(basket.flatMap((query) => providers.map((provider) => JSON.stringify([query.id, provider.trim().toLowerCase()]))));
|
|
@@ -84117,7 +84218,7 @@ var reportSweepsCheck = {
|
|
|
84117
84218
|
const graded = gradedReportMonths(reportMonths, now);
|
|
84118
84219
|
const months = reportMonths.map((month) => {
|
|
84119
84220
|
const bounds = calendarMonthBounds(month);
|
|
84120
|
-
const monthRuns = ctx.db.select().from(runs).where(
|
|
84221
|
+
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();
|
|
84121
84222
|
const evidence = monthRuns.map((run) => inspectSweep(ctx, run));
|
|
84122
84223
|
return {
|
|
84123
84224
|
month,
|
|
@@ -84140,7 +84241,7 @@ var reportSweepsCheck = {
|
|
|
84140
84241
|
};
|
|
84141
84242
|
|
|
84142
84243
|
// ../api-routes/src/doctor/checks/report-models.ts
|
|
84143
|
-
import { and as
|
|
84244
|
+
import { and as and63, eq as eq72, gte as gte18, inArray as inArray29, lte as lte19 } from "drizzle-orm";
|
|
84144
84245
|
var reportModelsCheck = {
|
|
84145
84246
|
id: "report.models",
|
|
84146
84247
|
category: CheckCategories.providers,
|
|
@@ -84155,7 +84256,7 @@ var reportModelsCheck = {
|
|
|
84155
84256
|
const months = reportMonths.flatMap((month) => {
|
|
84156
84257
|
const bounds = calendarMonthBounds(month);
|
|
84157
84258
|
const previous = new Date(Date.parse(bounds.since) - 1).toISOString().slice(0, 7);
|
|
84158
|
-
const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs,
|
|
84259
|
+
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();
|
|
84159
84260
|
return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
|
|
84160
84261
|
month,
|
|
84161
84262
|
previousMonth: previous,
|
|
@@ -84196,7 +84297,7 @@ var reportModelsCheck = {
|
|
|
84196
84297
|
};
|
|
84197
84298
|
|
|
84198
84299
|
// ../api-routes/src/doctor/checks/report-daily-data.ts
|
|
84199
|
-
import { eq as
|
|
84300
|
+
import { eq as eq73 } from "drizzle-orm";
|
|
84200
84301
|
var REPORTING_LAG_DAYS = 3;
|
|
84201
84302
|
function dateSpan(dates) {
|
|
84202
84303
|
const sorted = [...dates].sort();
|
|
@@ -84205,31 +84306,31 @@ function dateSpan(dates) {
|
|
|
84205
84306
|
function latestSyncRange(ctx, source) {
|
|
84206
84307
|
const projectId = ctx.project.id;
|
|
84207
84308
|
if (source === "ga") {
|
|
84208
|
-
const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(
|
|
84309
|
+
const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq73(gaDailyTotals.projectId, projectId)).all();
|
|
84209
84310
|
const latest2 = rows2.reduce((max2, row) => max2 === null || row.syncedAt > max2 ? row.syncedAt : max2, null);
|
|
84210
|
-
const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
84311
|
+
const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq73(gaTrafficSummaries.projectId, projectId)).get();
|
|
84211
84312
|
if (summary3 && (latest2 === null || summary3.syncedAt >= latest2)) return { start: summary3.start, end: summary3.end, syncedAt: summary3.syncedAt };
|
|
84212
84313
|
const span2 = dateSpan(rows2.filter((row) => row.syncedAt === latest2).map((row) => row.date));
|
|
84213
84314
|
return span2 && latest2 !== null ? { ...span2, syncedAt: latest2 } : null;
|
|
84214
84315
|
}
|
|
84215
|
-
const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(
|
|
84316
|
+
const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq73(gscDailyTotals.projectId, projectId)).all();
|
|
84216
84317
|
const latest = rows.reduce((max2, row) => max2 === null || row.createdAt > max2 ? row.createdAt : max2, null);
|
|
84217
84318
|
const span = dateSpan(rows.filter((row) => row.createdAt === latest).map((row) => row.date));
|
|
84218
84319
|
if (!span || latest === null) return null;
|
|
84219
|
-
const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(
|
|
84320
|
+
const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq73(gscDataWatermarks.projectId, projectId)).get();
|
|
84220
84321
|
return watermark?.through && watermark.updatedAt === latest && watermark.through > span.end ? { start: span.start, end: watermark.through, syncedAt: latest } : { ...span, syncedAt: latest };
|
|
84221
84322
|
}
|
|
84222
84323
|
function dailyCoverage(ctx, month, source) {
|
|
84223
84324
|
const project = ctx.project;
|
|
84224
84325
|
const connection = source === "ga" ? ctx.ga4CredentialStore?.getConnection(project.name) ?? ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4") : ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "gsc");
|
|
84225
|
-
const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(
|
|
84326
|
+
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();
|
|
84226
84327
|
if (!connection && stored.length === 0) return null;
|
|
84227
84328
|
const bounds = calendarMonthBounds(month);
|
|
84228
84329
|
const start = bounds.since.slice(0, 10);
|
|
84229
84330
|
const end = bounds.until.slice(0, 10);
|
|
84230
84331
|
const timeZone = source === "gsc" ? "America/Los_Angeles" : "UTC";
|
|
84231
84332
|
const matureThrough = isoDateDaysBeforeInTimeZone((/* @__PURE__ */ new Date()).toISOString(), REPORTING_LAG_DAYS, timeZone);
|
|
84232
|
-
const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(
|
|
84333
|
+
const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq73(projects.id, project.id)).get().date.slice(0, 10);
|
|
84233
84334
|
const connectedAt = connection?.createdAt?.slice(0, 10) ?? created;
|
|
84234
84335
|
const onboardingDate = [connectedAt, ...stored.map((row) => row.date)].sort()[0];
|
|
84235
84336
|
const byDate = new Map(stored.map((row) => [row.date, row.count]));
|
|
@@ -84311,7 +84412,7 @@ var reportDailyDataCheck = {
|
|
|
84311
84412
|
};
|
|
84312
84413
|
|
|
84313
84414
|
// ../api-routes/src/doctor/checks/referral-assessment.ts
|
|
84314
|
-
import { and as
|
|
84415
|
+
import { and as and64, eq as eq74, ne as ne14 } from "drizzle-orm";
|
|
84315
84416
|
var REFERRAL_ASSESSMENT_CHECKS = [{
|
|
84316
84417
|
id: "report.ai-referral-bursts",
|
|
84317
84418
|
category: CheckCategories.integrations,
|
|
@@ -84320,7 +84421,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
|
|
|
84320
84421
|
notificationPolicy: CheckNotificationPolicies.silent,
|
|
84321
84422
|
run: (ctx) => {
|
|
84322
84423
|
if (!ctx.project) return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.no-project", summary: "Select a project to inspect referral evidence." };
|
|
84323
|
-
const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(
|
|
84424
|
+
const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and64(eq74(trafficSources.projectId, ctx.project.id), ne14(trafficSources.status, TrafficSourceStatuses.archived))).get();
|
|
84324
84425
|
if (!configured) {
|
|
84325
84426
|
return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.not-configured", summary: "No non-archived server traffic source is configured for this project." };
|
|
84326
84427
|
}
|
|
@@ -84680,7 +84781,6 @@ var skillsTriggerSurfaceCheck = {
|
|
|
84680
84781
|
var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck, skillsTriggerSurfaceCheck];
|
|
84681
84782
|
|
|
84682
84783
|
// ../api-routes/src/doctor/checks/backlinks.ts
|
|
84683
|
-
import { and as and64, eq as eq74 } from "drizzle-orm";
|
|
84684
84784
|
function skippedNoProject() {
|
|
84685
84785
|
return {
|
|
84686
84786
|
status: CheckStatuses.skipped,
|
|
@@ -84696,36 +84796,58 @@ var BACKLINKS_CHECKS = [
|
|
|
84696
84796
|
title: "Backlinks source connected",
|
|
84697
84797
|
run: (ctx) => {
|
|
84698
84798
|
if (!ctx.project) return skippedNoProject();
|
|
84699
|
-
const
|
|
84700
|
-
const
|
|
84701
|
-
|
|
84702
|
-
|
|
84703
|
-
|
|
84704
|
-
|
|
84799
|
+
const state = readCommonCrawlSourceState(ctx.db, ctx.project.id);
|
|
84800
|
+
const details = {
|
|
84801
|
+
commoncrawl: state.connected,
|
|
84802
|
+
commoncrawlReadyRelease: state.hasReadyRelease,
|
|
84803
|
+
commoncrawlHasData: state.hasProjectData
|
|
84804
|
+
};
|
|
84805
|
+
if (!state.hasReadyRelease) {
|
|
84806
|
+
return {
|
|
84807
|
+
status: CheckStatuses.warn,
|
|
84808
|
+
code: "backlinks.source.none",
|
|
84809
|
+
summary: `No backlink source is set up for ${ctx.project.name}: no Common Crawl release has been synced.`,
|
|
84810
|
+
remediation: "Run `canonry backlinks sync` to download the newest Common Crawl release for the workspace.",
|
|
84811
|
+
details
|
|
84812
|
+
};
|
|
84813
|
+
}
|
|
84814
|
+
if (!state.hasProjectData) {
|
|
84705
84815
|
return {
|
|
84706
84816
|
status: CheckStatuses.warn,
|
|
84707
84817
|
code: "backlinks.source.none",
|
|
84708
|
-
summary: `
|
|
84709
|
-
remediation:
|
|
84710
|
-
details
|
|
84818
|
+
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.`,
|
|
84819
|
+
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.",
|
|
84820
|
+
details
|
|
84711
84821
|
};
|
|
84712
84822
|
}
|
|
84713
|
-
const
|
|
84714
|
-
eq74(backlinkSummaries.projectId, ctx.project.id),
|
|
84715
|
-
eq74(backlinkSummaries.source, BacklinkSources.commoncrawl)
|
|
84716
|
-
)).limit(1).get() : false;
|
|
84823
|
+
const connected = [BacklinkSources.commoncrawl];
|
|
84717
84824
|
return {
|
|
84718
84825
|
status: CheckStatuses.ok,
|
|
84719
84826
|
code: "backlinks.source.connected",
|
|
84720
84827
|
summary: "Common Crawl backlink source is set up.",
|
|
84721
|
-
remediation:
|
|
84722
|
-
details: {
|
|
84828
|
+
remediation: null,
|
|
84829
|
+
details: { ...details, connected }
|
|
84723
84830
|
};
|
|
84724
84831
|
}
|
|
84725
84832
|
}
|
|
84726
84833
|
];
|
|
84727
84834
|
|
|
84728
84835
|
// ../api-routes/src/doctor/checks/bing-auth.ts
|
|
84836
|
+
import { and as and65, desc as desc36, eq as eq75, inArray as inArray30 } from "drizzle-orm";
|
|
84837
|
+
var BING_CONNECTION_ENTITY = "bing_connection";
|
|
84838
|
+
var BING_CONNECTED_ACTION = "bing.connected";
|
|
84839
|
+
var BING_DISCONNECTED_ACTION = "bing.disconnected";
|
|
84840
|
+
function lostBingConnection(ctx, project) {
|
|
84841
|
+
const hasRow = (row) => row !== void 0;
|
|
84842
|
+
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());
|
|
84843
|
+
if (!storedData) return false;
|
|
84844
|
+
const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and65(
|
|
84845
|
+
eq75(auditLog.entityType, BING_CONNECTION_ENTITY),
|
|
84846
|
+
eq75(auditLog.entityId, project.canonicalDomain),
|
|
84847
|
+
inArray30(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
|
|
84848
|
+
)).orderBy(desc36(auditLog.createdAt)).limit(1).get();
|
|
84849
|
+
return lastChange?.action !== BING_DISCONNECTED_ACTION;
|
|
84850
|
+
}
|
|
84729
84851
|
var BING_AUTH_CHECKS = [
|
|
84730
84852
|
{
|
|
84731
84853
|
id: "bing.auth.connection",
|
|
@@ -84752,10 +84874,18 @@ var BING_AUTH_CHECKS = [
|
|
|
84752
84874
|
}
|
|
84753
84875
|
const conn = store.getConnection(ctx.project.canonicalDomain);
|
|
84754
84876
|
if (!conn) {
|
|
84877
|
+
if (!lostBingConnection(ctx, ctx.project)) {
|
|
84878
|
+
return {
|
|
84879
|
+
status: CheckStatuses.skipped,
|
|
84880
|
+
code: "bing.auth.not-connected",
|
|
84881
|
+
summary: `${ctx.project.name} has not connected Bing Webmaster Tools.`,
|
|
84882
|
+
remediation: `Optional: run \`canonry bing connect ${ctx.project.name} --api-key <key>\` to track Bing indexing.`
|
|
84883
|
+
};
|
|
84884
|
+
}
|
|
84755
84885
|
return {
|
|
84756
84886
|
status: CheckStatuses.fail,
|
|
84757
84887
|
code: "bing.auth.no-connection",
|
|
84758
|
-
summary: `No Bing connection for ${ctx.project.canonicalDomain}.`,
|
|
84888
|
+
summary: `No Bing connection for ${ctx.project.canonicalDomain}, but ${ctx.project.name} has stored Bing data from an earlier connection.`,
|
|
84759
84889
|
remediation: `Run \`canonry bing connect ${ctx.project.name} --api-key <key>\` to authorize.`
|
|
84760
84890
|
};
|
|
84761
84891
|
}
|
|
@@ -84872,7 +85002,7 @@ var BING_AUTH_CHECKS = [
|
|
|
84872
85002
|
];
|
|
84873
85003
|
|
|
84874
85004
|
// ../api-routes/src/doctor/checks/content.ts
|
|
84875
|
-
import { eq as
|
|
85005
|
+
import { eq as eq76 } from "drizzle-orm";
|
|
84876
85006
|
var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
|
|
84877
85007
|
var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
|
|
84878
85008
|
function skippedNoProject2() {
|
|
@@ -84885,13 +85015,17 @@ function skippedNoProject2() {
|
|
|
84885
85015
|
}
|
|
84886
85016
|
function loadProject(ctx) {
|
|
84887
85017
|
if (!ctx.project) return null;
|
|
84888
|
-
return ctx.db.select().from(projects).where(
|
|
85018
|
+
return ctx.db.select().from(projects).where(eq76(projects.id, ctx.project.id)).get() ?? null;
|
|
84889
85019
|
}
|
|
84890
85020
|
var winnabilityCoverageCheck = {
|
|
84891
85021
|
id: "content.winnability.coverage",
|
|
84892
85022
|
category: CheckCategories.integrations,
|
|
84893
85023
|
scope: CheckScopes.project,
|
|
84894
85024
|
title: "Content winnability classification coverage",
|
|
85025
|
+
// Advice about content targeting, not a broken instrument. Most projects sit
|
|
85026
|
+
// below the threshold for good, and a check that never clears keeps the
|
|
85027
|
+
// project permanently degraded, so no recovery could ever be sent.
|
|
85028
|
+
notificationPolicy: CheckNotificationPolicies.silent,
|
|
84895
85029
|
run: (ctx) => {
|
|
84896
85030
|
if (!ctx.project) return skippedNoProject2();
|
|
84897
85031
|
const project = loadProject(ctx);
|
|
@@ -84974,7 +85108,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
|
|
|
84974
85108
|
);
|
|
84975
85109
|
|
|
84976
85110
|
// ../api-routes/src/doctor/checks/ads.ts
|
|
84977
|
-
import { eq as
|
|
85111
|
+
import { eq as eq77 } from "drizzle-orm";
|
|
84978
85112
|
var RECENT_SYNC_WARN_DAYS = 7;
|
|
84979
85113
|
var RECENT_SYNC_FAIL_DAYS = 30;
|
|
84980
85114
|
var adsConnectionCheck = {
|
|
@@ -84991,7 +85125,7 @@ var adsConnectionCheck = {
|
|
|
84991
85125
|
remediation: null
|
|
84992
85126
|
};
|
|
84993
85127
|
}
|
|
84994
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
85128
|
+
const row = ctx.db.select().from(adsConnections).where(eq77(adsConnections.projectId, ctx.project.id)).get();
|
|
84995
85129
|
if (!row) {
|
|
84996
85130
|
return {
|
|
84997
85131
|
status: CheckStatuses.skipped,
|
|
@@ -85041,7 +85175,7 @@ var adsRecentSyncCheck = {
|
|
|
85041
85175
|
remediation: null
|
|
85042
85176
|
};
|
|
85043
85177
|
}
|
|
85044
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
85178
|
+
const row = ctx.db.select().from(adsConnections).where(eq77(adsConnections.projectId, ctx.project.id)).get();
|
|
85045
85179
|
if (!row) {
|
|
85046
85180
|
return {
|
|
85047
85181
|
status: CheckStatuses.skipped,
|
|
@@ -85251,7 +85385,7 @@ var ga4ConnectionCheck = {
|
|
|
85251
85385
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
85252
85386
|
|
|
85253
85387
|
// ../api-routes/src/doctor/checks/data-freshness.ts
|
|
85254
|
-
import { and as
|
|
85388
|
+
import { and as and66, desc as desc37, eq as eq78, inArray as inArray31 } from "drizzle-orm";
|
|
85255
85389
|
var GA_DATA_AGING_DAYS = 3;
|
|
85256
85390
|
var GA_DATA_STALE_DAYS = 5;
|
|
85257
85391
|
var GSC_DATA_AGING_DAYS = 5;
|
|
@@ -85279,7 +85413,7 @@ function skipped(code, summary3) {
|
|
|
85279
85413
|
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
85280
85414
|
}
|
|
85281
85415
|
function lastSyncAgeDays(ctx, kinds) {
|
|
85282
|
-
const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(
|
|
85416
|
+
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();
|
|
85283
85417
|
if (!row?.createdAt) return null;
|
|
85284
85418
|
const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS2);
|
|
85285
85419
|
return Number.isFinite(age) ? age : null;
|
|
@@ -85375,7 +85509,7 @@ var gaRecentDataCheck = {
|
|
|
85375
85509
|
const connection = gaConnected(ctx);
|
|
85376
85510
|
if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
|
|
85377
85511
|
if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
|
|
85378
|
-
const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(
|
|
85512
|
+
const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq78(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc37(gaDailyTotals.date)).limit(1).get();
|
|
85379
85513
|
return grade(ctx, GA_SPEC, newest?.date);
|
|
85380
85514
|
}
|
|
85381
85515
|
};
|
|
@@ -85390,9 +85524,9 @@ var gscRecentDataCheck = {
|
|
|
85390
85524
|
if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
|
|
85391
85525
|
return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
|
|
85392
85526
|
}
|
|
85393
|
-
const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(
|
|
85527
|
+
const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq78(gscDataWatermarks.projectId, ctx.project.id)).get();
|
|
85394
85528
|
if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
|
|
85395
|
-
const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(
|
|
85529
|
+
const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq78(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc37(gscDailyTotals.date)).limit(1).get();
|
|
85396
85530
|
return grade(ctx, GSC_SPEC, newest?.date);
|
|
85397
85531
|
}
|
|
85398
85532
|
};
|
|
@@ -85660,7 +85794,7 @@ var siteReachabilityCheck = {
|
|
|
85660
85794
|
var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
|
|
85661
85795
|
|
|
85662
85796
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
85663
|
-
import { and as
|
|
85797
|
+
import { and as and67, eq as eq79 } from "drizzle-orm";
|
|
85664
85798
|
var RECENT_SYNC_WARN_DAYS2 = 4;
|
|
85665
85799
|
var RECENT_SYNC_FAIL_DAYS2 = 30;
|
|
85666
85800
|
function skippedNoProject3() {
|
|
@@ -85679,10 +85813,25 @@ function storeUnavailable() {
|
|
|
85679
85813
|
remediation: null
|
|
85680
85814
|
};
|
|
85681
85815
|
}
|
|
85816
|
+
function hasSelectedLocation(ctx, projectId) {
|
|
85817
|
+
return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and67(eq79(gbpLocations.projectId, projectId), eq79(gbpLocations.selected, true))).limit(1).get();
|
|
85818
|
+
}
|
|
85682
85819
|
async function resolveGbpToken(ctx) {
|
|
85683
85820
|
if (!ctx.project) return { ok: false, output: skippedNoProject3() };
|
|
85684
85821
|
const store = ctx.googleConnectionStore;
|
|
85685
85822
|
if (!store) return { ok: false, output: storeUnavailable() };
|
|
85823
|
+
const conn = store.getConnection(ctx.project.canonicalDomain, "gbp");
|
|
85824
|
+
if (!conn && !hasSelectedLocation(ctx, ctx.project.id)) {
|
|
85825
|
+
return {
|
|
85826
|
+
ok: false,
|
|
85827
|
+
output: {
|
|
85828
|
+
status: CheckStatuses.skipped,
|
|
85829
|
+
code: "gbp.auth.not-connected",
|
|
85830
|
+
summary: `${ctx.project.name} has not connected Google Business Profile.`,
|
|
85831
|
+
remediation: `Optional: run \`canonry gbp connect ${ctx.project.name}\` to track a Business Profile.`
|
|
85832
|
+
}
|
|
85833
|
+
};
|
|
85834
|
+
}
|
|
85686
85835
|
const auth = ctx.getGoogleAuthConfig?.() ?? {};
|
|
85687
85836
|
if (!auth.clientId || !auth.clientSecret) {
|
|
85688
85837
|
return {
|
|
@@ -85695,14 +85844,13 @@ async function resolveGbpToken(ctx) {
|
|
|
85695
85844
|
}
|
|
85696
85845
|
};
|
|
85697
85846
|
}
|
|
85698
|
-
const conn = store.getConnection(ctx.project.canonicalDomain, "gbp");
|
|
85699
85847
|
if (!conn) {
|
|
85700
85848
|
return {
|
|
85701
85849
|
ok: false,
|
|
85702
85850
|
output: {
|
|
85703
85851
|
status: CheckStatuses.fail,
|
|
85704
85852
|
code: "gbp.auth.no-connection",
|
|
85705
|
-
summary: `No Google Business Profile connection for ${ctx.project.canonicalDomain}.`,
|
|
85853
|
+
summary: `No Google Business Profile connection for ${ctx.project.canonicalDomain}, but ${ctx.project.name} still has selected locations.`,
|
|
85706
85854
|
remediation: `Run \`canonry gbp connect ${ctx.project.name}\` to authorize.`
|
|
85707
85855
|
}
|
|
85708
85856
|
};
|
|
@@ -85916,7 +86064,7 @@ var recentSyncCheck = {
|
|
|
85916
86064
|
title: "GBP recent sync",
|
|
85917
86065
|
run: (ctx) => {
|
|
85918
86066
|
if (!ctx.project) return skippedNoProject3();
|
|
85919
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
86067
|
+
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();
|
|
85920
86068
|
if (selected.length === 0) {
|
|
85921
86069
|
return {
|
|
85922
86070
|
status: CheckStatuses.skipped,
|
|
@@ -85976,7 +86124,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
85976
86124
|
);
|
|
85977
86125
|
|
|
85978
86126
|
// ../api-routes/src/doctor/checks/places.ts
|
|
85979
|
-
import { eq as
|
|
86127
|
+
import { eq as eq80 } from "drizzle-orm";
|
|
85980
86128
|
var apiKeyCheck = {
|
|
85981
86129
|
id: "gbp.places.api-key",
|
|
85982
86130
|
category: CheckCategories.auth,
|
|
@@ -86021,7 +86169,7 @@ var apiKeyCheck = {
|
|
|
86021
86169
|
details: { tier: cfg.tier }
|
|
86022
86170
|
};
|
|
86023
86171
|
}
|
|
86024
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
86172
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq80(gbpLocations.projectId, ctx.project.id)).all();
|
|
86025
86173
|
const selected = rows.filter((r) => r.selected);
|
|
86026
86174
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
86027
86175
|
const details = {
|
|
@@ -86054,7 +86202,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
|
|
|
86054
86202
|
);
|
|
86055
86203
|
|
|
86056
86204
|
// ../api-routes/src/doctor/checks/gbp-reviews.ts
|
|
86057
|
-
import { and as
|
|
86205
|
+
import { and as and68, eq as eq81 } from "drizzle-orm";
|
|
86058
86206
|
var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
|
|
86059
86207
|
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`.";
|
|
86060
86208
|
var reviewsAccessCheck = {
|
|
@@ -86082,7 +86230,7 @@ var reviewsAccessCheck = {
|
|
|
86082
86230
|
reviewsAccess: gbpLocations.reviewsAccess,
|
|
86083
86231
|
reviewsAccessReason: gbpLocations.reviewsAccessReason,
|
|
86084
86232
|
reviewsCheckedAt: gbpLocations.reviewsCheckedAt
|
|
86085
|
-
}).from(gbpLocations).where(
|
|
86233
|
+
}).from(gbpLocations).where(and68(eq81(gbpLocations.projectId, project.id), eq81(gbpLocations.selected, true))).all();
|
|
86086
86234
|
if (locations.length === 0) {
|
|
86087
86235
|
return {
|
|
86088
86236
|
status: CheckStatuses.skipped,
|
|
@@ -86102,7 +86250,7 @@ var reviewsAccessCheck = {
|
|
|
86102
86250
|
const places = ctx.getPlacesConfig?.();
|
|
86103
86251
|
const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
|
|
86104
86252
|
const v4Worked = new Set(
|
|
86105
|
-
ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(
|
|
86253
|
+
ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and68(eq81(gbpReviewRatings.projectId, project.id), eq81(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
|
|
86106
86254
|
);
|
|
86107
86255
|
const coverageOf = (loc) => {
|
|
86108
86256
|
if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
|
|
@@ -86136,7 +86284,7 @@ var reviewsAccessCheck = {
|
|
|
86136
86284
|
none > 0 ? `${none} have no review source` : null
|
|
86137
86285
|
].filter((part) => part !== null);
|
|
86138
86286
|
const summary3 = `Business Profile reviews are not available${why}. ${parts.join("; ")}.`;
|
|
86139
|
-
const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(
|
|
86287
|
+
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)));
|
|
86140
86288
|
const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
|
|
86141
86289
|
if (none > 0 && subscribed) {
|
|
86142
86290
|
return {
|
|
@@ -86694,7 +86842,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
86694
86842
|
];
|
|
86695
86843
|
|
|
86696
86844
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
86697
|
-
import { and as
|
|
86845
|
+
import { and as and69, eq as eq82, gte as gte19, inArray as inArray32, ne as ne15, sql as sql29 } from "drizzle-orm";
|
|
86698
86846
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
86699
86847
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
86700
86848
|
function isCloudflareDirectPush(source) {
|
|
@@ -86733,8 +86881,8 @@ function skippedNoProject5() {
|
|
|
86733
86881
|
function loadProbes(ctx) {
|
|
86734
86882
|
if (!ctx.project) return [];
|
|
86735
86883
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
86736
|
-
|
|
86737
|
-
|
|
86884
|
+
and69(
|
|
86885
|
+
eq82(trafficSources.projectId, ctx.project.id),
|
|
86738
86886
|
ne15(trafficSources.status, TrafficSourceStatuses.archived)
|
|
86739
86887
|
)
|
|
86740
86888
|
).all();
|
|
@@ -86840,27 +86988,27 @@ var recentDataCheck = {
|
|
|
86840
86988
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
86841
86989
|
const recentCrawlers = Number(
|
|
86842
86990
|
ctx.db.select({ total: sql29`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
86843
|
-
|
|
86844
|
-
|
|
86845
|
-
|
|
86991
|
+
and69(
|
|
86992
|
+
eq82(crawlerEventsHourly.projectId, ctx.project.id),
|
|
86993
|
+
inArray32(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
86846
86994
|
gte19(crawlerEventsHourly.tsHour, warnCutoff)
|
|
86847
86995
|
)
|
|
86848
86996
|
).get()?.total ?? 0
|
|
86849
86997
|
);
|
|
86850
86998
|
const recentReferrals = Number(
|
|
86851
86999
|
ctx.db.select({ total: sql29`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
86852
|
-
|
|
86853
|
-
|
|
86854
|
-
|
|
87000
|
+
and69(
|
|
87001
|
+
eq82(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
87002
|
+
inArray32(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
86855
87003
|
gte19(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
86856
87004
|
)
|
|
86857
87005
|
).get()?.total ?? 0
|
|
86858
87006
|
);
|
|
86859
87007
|
const recentUserFetches = Number(
|
|
86860
87008
|
ctx.db.select({ total: sql29`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
86861
|
-
|
|
86862
|
-
|
|
86863
|
-
|
|
87009
|
+
and69(
|
|
87010
|
+
eq82(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
87011
|
+
inArray32(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
86864
87012
|
gte19(aiUserFetchEventsHourly.tsHour, warnCutoff)
|
|
86865
87013
|
)
|
|
86866
87014
|
).get()?.total ?? 0
|
|
@@ -86884,27 +87032,27 @@ var recentDataCheck = {
|
|
|
86884
87032
|
}
|
|
86885
87033
|
const olderCrawlers = Number(
|
|
86886
87034
|
ctx.db.select({ total: sql29`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
86887
|
-
|
|
86888
|
-
|
|
86889
|
-
|
|
87035
|
+
and69(
|
|
87036
|
+
eq82(crawlerEventsHourly.projectId, ctx.project.id),
|
|
87037
|
+
inArray32(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
86890
87038
|
gte19(crawlerEventsHourly.tsHour, failCutoff)
|
|
86891
87039
|
)
|
|
86892
87040
|
).get()?.total ?? 0
|
|
86893
87041
|
);
|
|
86894
87042
|
const olderReferrals = Number(
|
|
86895
87043
|
ctx.db.select({ total: sql29`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
86896
|
-
|
|
86897
|
-
|
|
86898
|
-
|
|
87044
|
+
and69(
|
|
87045
|
+
eq82(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
87046
|
+
inArray32(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
86899
87047
|
gte19(aiReferralEventsHourly.tsHour, failCutoff)
|
|
86900
87048
|
)
|
|
86901
87049
|
).get()?.total ?? 0
|
|
86902
87050
|
);
|
|
86903
87051
|
const olderUserFetches = Number(
|
|
86904
87052
|
ctx.db.select({ total: sql29`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
86905
|
-
|
|
86906
|
-
|
|
86907
|
-
|
|
87053
|
+
and69(
|
|
87054
|
+
eq82(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
87055
|
+
inArray32(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
86908
87056
|
gte19(aiUserFetchEventsHourly.tsHour, failCutoff)
|
|
86909
87057
|
)
|
|
86910
87058
|
).get()?.total ?? 0
|
|
@@ -87078,6 +87226,7 @@ var cacheBlindSpotCheck = {
|
|
|
87078
87226
|
category: CheckCategories.integrations,
|
|
87079
87227
|
scope: CheckScopes.project,
|
|
87080
87228
|
title: "WordPress traffic cache blind spot",
|
|
87229
|
+
notificationPolicy: CheckNotificationPolicies.silent,
|
|
87081
87230
|
run: (ctx) => {
|
|
87082
87231
|
if (!ctx.project) return skippedNoProject5();
|
|
87083
87232
|
const wpSources = loadProbes(ctx).filter(
|
|
@@ -87677,7 +87826,7 @@ var GOOGLE_MARKETING_DOCTOR_CHECKS = createGoogleMarketingDoctorChecks(
|
|
|
87677
87826
|
);
|
|
87678
87827
|
|
|
87679
87828
|
// ../api-routes/src/doctor/checks/integration-sync.ts
|
|
87680
|
-
import { and as
|
|
87829
|
+
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";
|
|
87681
87830
|
var SYNC_FAILURE_WINDOW = 3;
|
|
87682
87831
|
var SUMMARY_ERROR_LIMIT = 300;
|
|
87683
87832
|
var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
|
|
@@ -87697,7 +87846,7 @@ function gscConnection(ctx, project) {
|
|
|
87697
87846
|
function gbpConnection(ctx, project) {
|
|
87698
87847
|
if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
|
|
87699
87848
|
if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
|
|
87700
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(
|
|
87849
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and70(eq83(gbpLocations.projectId, project.id), eq83(gbpLocations.selected, true))).limit(1).get();
|
|
87701
87850
|
return selected ? CONNECTED : { state: "no-selected-locations" };
|
|
87702
87851
|
}
|
|
87703
87852
|
function gaConnection(ctx, project) {
|
|
@@ -87775,20 +87924,20 @@ function gradeSyncFailures(ctx, spec) {
|
|
|
87775
87924
|
return skipped3(code("no-selected-locations"), `No ${spec.label} locations are selected for this project.`);
|
|
87776
87925
|
}
|
|
87777
87926
|
const nothingToSync = spec.nothingToSyncErrors ?? [];
|
|
87778
|
-
const scope =
|
|
87779
|
-
|
|
87780
|
-
|
|
87781
|
-
...nothingToSync.length > 0 ? [
|
|
87927
|
+
const scope = and70(
|
|
87928
|
+
eq83(runs.projectId, project.id),
|
|
87929
|
+
eq83(runs.kind, spec.kind),
|
|
87930
|
+
...nothingToSync.length > 0 ? [or19(isNull14(runs.error), notInArray2(runs.error, [...nothingToSync]))] : []
|
|
87782
87931
|
);
|
|
87783
|
-
const graded = connection.since ?
|
|
87784
|
-
const recent = ctx.db.select({ status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(
|
|
87932
|
+
const graded = connection.since ? and70(scope, gte20(runs.createdAt, connection.since)) : scope;
|
|
87933
|
+
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();
|
|
87785
87934
|
if (recent.length === 0) {
|
|
87786
87935
|
return skipped3(code("no-runs"), `${spec.label} is connected but no sync has finished yet.`);
|
|
87787
87936
|
}
|
|
87788
|
-
const lastSuccess = ctx.db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
87789
|
-
const failuresSinceSuccess = ctx.db.select({ value: count3() }).from(runs).where(
|
|
87937
|
+
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();
|
|
87938
|
+
const failuresSinceSuccess = ctx.db.select({ value: count3() }).from(runs).where(and70(
|
|
87790
87939
|
graded,
|
|
87791
|
-
|
|
87940
|
+
eq83(runs.status, RunStatuses.failed),
|
|
87792
87941
|
...lastSuccess ? [gt7(runs.createdAt, lastSuccess.createdAt)] : []
|
|
87793
87942
|
)).get()?.value ?? 0;
|
|
87794
87943
|
const lastSuccessAt = lastSuccess ? lastSuccess.finishedAt ?? lastSuccess.createdAt : null;
|
|
@@ -88102,7 +88251,7 @@ async function doctorRoutes(app, opts) {
|
|
|
88102
88251
|
|
|
88103
88252
|
// ../api-routes/src/discovery/routes.ts
|
|
88104
88253
|
import crypto50 from "crypto";
|
|
88105
|
-
import { and as
|
|
88254
|
+
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";
|
|
88106
88255
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
88107
88256
|
async function discoveryRoutes(app, opts) {
|
|
88108
88257
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -88136,13 +88285,13 @@ async function discoveryRoutes(app, opts) {
|
|
|
88136
88285
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
88137
88286
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
88138
88287
|
const decision = app.db.transaction((tx) => {
|
|
88139
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
88140
|
-
|
|
88141
|
-
|
|
88288
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and71(
|
|
88289
|
+
eq84(discoverySessions.projectId, project.id),
|
|
88290
|
+
eq84(discoverySessions.icpDescription, icpDescription),
|
|
88142
88291
|
// Buyer is part of session identity: it changes the seed prompt's
|
|
88143
88292
|
// semantics, so a request with a different (or no) buyer must start
|
|
88144
88293
|
// its own session, never adopt another buyer's probes.
|
|
88145
|
-
parsed.data.buyerDescription == null ? isNull15(discoverySessions.buyerDescription) :
|
|
88294
|
+
parsed.data.buyerDescription == null ? isNull15(discoverySessions.buyerDescription) : eq84(discoverySessions.buyerDescription, parsed.data.buyerDescription),
|
|
88146
88295
|
// Locations are identity too: a different service-area subset seeds
|
|
88147
88296
|
// and probes a different geo, so it must never reuse another geo's
|
|
88148
88297
|
// session. resolveLocations is deterministic (project-config order),
|
|
@@ -88152,18 +88301,18 @@ async function discoveryRoutes(app, opts) {
|
|
|
88152
88301
|
// locations a NULL row's subset is unknowable, so it conservatively
|
|
88153
88302
|
// never reuses — a one-time, bounded (2h window) non-reuse after
|
|
88154
88303
|
// upgrade, never a wrong reuse.
|
|
88155
|
-
locations.length === 0 ?
|
|
88304
|
+
locations.length === 0 ? or20(isNull15(discoverySessions.locations), eq84(discoverySessions.locations, locations)) : eq84(discoverySessions.locations, locations),
|
|
88156
88305
|
// Seed provider set is identity: a different phrasing distribution
|
|
88157
88306
|
// must never reuse another set's session. Null (= Gemini-only
|
|
88158
88307
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
88159
|
-
seedProviders == null ? isNull15(discoverySessions.seedProviders) :
|
|
88160
|
-
|
|
88308
|
+
seedProviders == null ? isNull15(discoverySessions.seedProviders) : eq84(discoverySessions.seedProviders, seedProviders),
|
|
88309
|
+
inArray34(discoverySessions.status, [
|
|
88161
88310
|
DiscoverySessionStatuses.queued,
|
|
88162
88311
|
DiscoverySessionStatuses.seeding,
|
|
88163
88312
|
DiscoverySessionStatuses.probing
|
|
88164
88313
|
]),
|
|
88165
88314
|
gte21(discoverySessions.createdAt, ageFloorIso)
|
|
88166
|
-
)).orderBy(
|
|
88315
|
+
)).orderBy(desc39(discoverySessions.createdAt)).get();
|
|
88167
88316
|
if (existing && existing.runId) {
|
|
88168
88317
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
88169
88318
|
}
|
|
@@ -88232,7 +88381,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88232
88381
|
const project = resolveProject(app.db, request.params.name);
|
|
88233
88382
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
88234
88383
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
88235
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
88384
|
+
const rows = app.db.select().from(discoverySessions).where(eq84(discoverySessions.projectId, project.id)).orderBy(desc39(discoverySessions.createdAt)).limit(limit).all();
|
|
88236
88385
|
return reply.send(rows.map(serializeSession));
|
|
88237
88386
|
}
|
|
88238
88387
|
);
|
|
@@ -88240,11 +88389,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
88240
88389
|
"/projects/:name/discover/sessions/:id",
|
|
88241
88390
|
async (request, reply) => {
|
|
88242
88391
|
const project = resolveProject(app.db, request.params.name);
|
|
88243
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
88392
|
+
const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
|
|
88244
88393
|
if (!session || session.projectId !== project.id) {
|
|
88245
88394
|
throw notFound("Discovery session", request.params.id);
|
|
88246
88395
|
}
|
|
88247
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
88396
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
|
|
88248
88397
|
const detail = {
|
|
88249
88398
|
...serializeSession(session),
|
|
88250
88399
|
probes: probeRows.map(serializeProbe)
|
|
@@ -88256,7 +88405,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88256
88405
|
"/projects/:name/discover/sessions/:id/harvest",
|
|
88257
88406
|
async (request, reply) => {
|
|
88258
88407
|
const project = resolveProject(app.db, request.params.name);
|
|
88259
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
88408
|
+
const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
|
|
88260
88409
|
if (!session || session.projectId !== project.id) {
|
|
88261
88410
|
throw notFound("Discovery session", request.params.id);
|
|
88262
88411
|
}
|
|
@@ -88264,7 +88413,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88264
88413
|
const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
|
|
88265
88414
|
const applyAnchor = request.query.anchor !== "false";
|
|
88266
88415
|
const provider = session.seedProvider ?? "gemini";
|
|
88267
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
88416
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
|
|
88268
88417
|
const extract = opts.harvestSearchQueries;
|
|
88269
88418
|
const probesWithQueries = probeRows.map((row) => {
|
|
88270
88419
|
if (!extract || !row.rawResponse) return { searchQueries: [] };
|
|
@@ -88275,7 +88424,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88275
88424
|
return { searchQueries: [] };
|
|
88276
88425
|
}
|
|
88277
88426
|
});
|
|
88278
|
-
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(
|
|
88427
|
+
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq84(queries.projectId, project.id)).all().map((r) => r.query);
|
|
88279
88428
|
const anchorTerms = buildHarvestAnchorTerms(
|
|
88280
88429
|
[session.icpDescription ?? "", ...trackedQueries],
|
|
88281
88430
|
effectiveDomains(project)
|
|
@@ -88322,11 +88471,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
88322
88471
|
"/projects/:name/discover/sessions/:id/promote",
|
|
88323
88472
|
async (request, reply) => {
|
|
88324
88473
|
const project = resolveProject(app.db, request.params.name);
|
|
88325
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
88474
|
+
const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
|
|
88326
88475
|
if (!session || session.projectId !== project.id) {
|
|
88327
88476
|
throw notFound("Discovery session", request.params.id);
|
|
88328
88477
|
}
|
|
88329
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
88478
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
|
|
88330
88479
|
const cited = /* @__PURE__ */ new Set();
|
|
88331
88480
|
const aspirational = /* @__PURE__ */ new Set();
|
|
88332
88481
|
const wasted = /* @__PURE__ */ new Set();
|
|
@@ -88361,7 +88510,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88361
88510
|
);
|
|
88362
88511
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
88363
88512
|
const project = resolveProject(app.db, request.params.name);
|
|
88364
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
88513
|
+
const session = app.db.select().from(discoverySessions).where(eq84(discoverySessions.id, request.params.id)).get();
|
|
88365
88514
|
if (!session || session.projectId !== project.id) {
|
|
88366
88515
|
throw notFound("Discovery session", request.params.id);
|
|
88367
88516
|
}
|
|
@@ -88384,7 +88533,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88384
88533
|
const bucketSet = new Set(buckets);
|
|
88385
88534
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
88386
88535
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
88387
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
88536
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq84(discoveryProbes.sessionId, session.id)).all();
|
|
88388
88537
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
88389
88538
|
for (const probe of probeRows) {
|
|
88390
88539
|
if (!probe.bucket) continue;
|
|
@@ -88392,7 +88541,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
88392
88541
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
88393
88542
|
}
|
|
88394
88543
|
const existingQueries = new Set(
|
|
88395
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
88544
|
+
app.db.select({ query: queries.query }).from(queries).where(eq84(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
88396
88545
|
);
|
|
88397
88546
|
const promotedQueries = [];
|
|
88398
88547
|
const skippedQueries = [];
|
|
@@ -88605,7 +88754,7 @@ function planCompetitorPromotion(input) {
|
|
|
88605
88754
|
|
|
88606
88755
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
88607
88756
|
import crypto51 from "crypto";
|
|
88608
|
-
import { eq as
|
|
88757
|
+
import { eq as eq85 } from "drizzle-orm";
|
|
88609
88758
|
var DEFAULT_MAX_PROBES = 100;
|
|
88610
88759
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
88611
88760
|
function classifyProbeBucket(input) {
|
|
@@ -88690,7 +88839,7 @@ async function executeDiscovery(opts) {
|
|
|
88690
88839
|
status: DiscoverySessionStatuses.seeding,
|
|
88691
88840
|
dedupThreshold,
|
|
88692
88841
|
startedAt
|
|
88693
|
-
}).where(
|
|
88842
|
+
}).where(eq85(discoverySessions.id, opts.sessionId)).run();
|
|
88694
88843
|
const seedResult = await opts.deps.seed({
|
|
88695
88844
|
project: opts.project,
|
|
88696
88845
|
icpDescription: opts.icpDescription,
|
|
@@ -88751,7 +88900,7 @@ async function executeDiscovery(opts) {
|
|
|
88751
88900
|
dedupBandPairFraction: dedupStats.bandPairFraction,
|
|
88752
88901
|
dedupPairsTotal: dedupStats.pairsTotal,
|
|
88753
88902
|
warning
|
|
88754
|
-
}).where(
|
|
88903
|
+
}).where(eq85(discoverySessions.id, opts.sessionId)).run();
|
|
88755
88904
|
const probeLocation = opts.locations?.[0];
|
|
88756
88905
|
const probeResults = await mapWithConcurrency(
|
|
88757
88906
|
probedCanonicals,
|
|
@@ -88805,7 +88954,7 @@ async function executeDiscovery(opts) {
|
|
|
88805
88954
|
wastedCount: buckets["wasted-surface"],
|
|
88806
88955
|
competitorMap,
|
|
88807
88956
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
88808
|
-
}).where(
|
|
88957
|
+
}).where(eq85(discoverySessions.id, opts.sessionId)).run();
|
|
88809
88958
|
upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
|
|
88810
88959
|
return {
|
|
88811
88960
|
buckets,
|
|
@@ -88845,7 +88994,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
88845
88994
|
status: DiscoverySessionStatuses.failed,
|
|
88846
88995
|
error,
|
|
88847
88996
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
88848
|
-
}).where(
|
|
88997
|
+
}).where(eq85(discoverySessions.id, sessionId)).run();
|
|
88849
88998
|
}
|
|
88850
88999
|
function dedupeStrings(input) {
|
|
88851
89000
|
const seen2 = /* @__PURE__ */ new Set();
|
|
@@ -88863,7 +89012,7 @@ function dedupeStrings(input) {
|
|
|
88863
89012
|
|
|
88864
89013
|
// ../api-routes/src/technical-aeo.ts
|
|
88865
89014
|
import crypto52 from "crypto";
|
|
88866
|
-
import { and as
|
|
89015
|
+
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";
|
|
88867
89016
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
88868
89017
|
var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
|
|
88869
89018
|
...SiteCrawlFetchedStates,
|
|
@@ -89100,8 +89249,8 @@ function parseLinkKind(value) {
|
|
|
89100
89249
|
return allowed.data;
|
|
89101
89250
|
}
|
|
89102
89251
|
function linkKindFilter(linkKind) {
|
|
89103
|
-
if (linkKind === SiteHealthLinkKinds.content) return
|
|
89104
|
-
if (linkKind === SiteHealthLinkKinds.template) return
|
|
89252
|
+
if (linkKind === SiteHealthLinkKinds.content) return eq86(siteCrawlEdges.isTemplate, false);
|
|
89253
|
+
if (linkKind === SiteHealthLinkKinds.template) return eq86(siteCrawlEdges.isTemplate, true);
|
|
89105
89254
|
return void 0;
|
|
89106
89255
|
}
|
|
89107
89256
|
var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
|
|
@@ -89205,20 +89354,20 @@ function changedFields(before, after, fields) {
|
|
|
89205
89354
|
}
|
|
89206
89355
|
async function technicalAeoRoutes(app, opts) {
|
|
89207
89356
|
const visibleCrawlFilters = (projectId) => [
|
|
89208
|
-
|
|
89209
|
-
|
|
89210
|
-
|
|
89211
|
-
|
|
89357
|
+
eq86(siteCrawlSnapshots.projectId, projectId),
|
|
89358
|
+
eq86(runs.projectId, projectId),
|
|
89359
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
89360
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
89212
89361
|
notProbeRun()
|
|
89213
89362
|
];
|
|
89214
89363
|
const availableScanDatesFor = (projectId, requestedDate) => {
|
|
89215
89364
|
if (!requestedDate) return void 0;
|
|
89216
89365
|
const date = sql30`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
|
|
89217
89366
|
const filters = visibleCrawlFilters(projectId);
|
|
89218
|
-
const allDates =
|
|
89219
|
-
const matchingDates =
|
|
89220
|
-
const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
89221
|
-
const countDates = (where) => app.db.select({ value: sql30`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
89367
|
+
const allDates = and72(...filters);
|
|
89368
|
+
const matchingDates = and72(...filters, eq86(sql30`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
|
|
89369
|
+
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);
|
|
89370
|
+
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;
|
|
89222
89371
|
return {
|
|
89223
89372
|
recentDates: readDates(allDates),
|
|
89224
89373
|
totalDates: countDates(allDates),
|
|
@@ -89235,33 +89384,33 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89235
89384
|
filters.push(gte22(siteCrawlSnapshots.createdAt, requestedDate), lt13(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
|
|
89236
89385
|
}
|
|
89237
89386
|
const selectionFilters = scope.scoredOnly ? [...filters, sql30`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
|
|
89238
|
-
const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
89239
|
-
|
|
89240
|
-
|
|
89241
|
-
|
|
89242
|
-
|
|
89243
|
-
|
|
89387
|
+
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(
|
|
89388
|
+
desc40(sql30`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
|
|
89389
|
+
desc40(siteCrawlSnapshots.complete),
|
|
89390
|
+
desc40(siteCrawlSnapshots.pagesFetched),
|
|
89391
|
+
desc40(siteCrawlSnapshots.createdAt),
|
|
89392
|
+
desc40(siteCrawlSnapshots.runId)
|
|
89244
89393
|
).limit(1).get();
|
|
89245
89394
|
if (!selected) return void 0;
|
|
89246
89395
|
const date = selected.snapshot.createdAt.slice(0, 10);
|
|
89247
89396
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
89248
89397
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
89249
|
-
const sameDate =
|
|
89398
|
+
const sameDate = and72(
|
|
89250
89399
|
...filters,
|
|
89251
89400
|
gte22(siteCrawlSnapshots.createdAt, date),
|
|
89252
89401
|
lt13(siteCrawlSnapshots.createdAt, nextDate.toISOString())
|
|
89253
89402
|
);
|
|
89254
|
-
const sameDateRunCount = app.db.select({ value: count4() }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
89403
|
+
const sameDateRunCount = app.db.select({ value: count4() }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
89255
89404
|
const candidates = app.db.select({
|
|
89256
89405
|
runId: siteCrawlSnapshots.runId,
|
|
89257
89406
|
createdAt: siteCrawlSnapshots.createdAt,
|
|
89258
89407
|
complete: siteCrawlSnapshots.complete,
|
|
89259
89408
|
pages: siteCrawlSnapshots.pagesFetched
|
|
89260
|
-
}).from(siteCrawlSnapshots).innerJoin(runs,
|
|
89261
|
-
|
|
89262
|
-
|
|
89263
|
-
|
|
89264
|
-
|
|
89409
|
+
}).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
89410
|
+
desc40(siteCrawlSnapshots.complete),
|
|
89411
|
+
desc40(siteCrawlSnapshots.pagesFetched),
|
|
89412
|
+
desc40(siteCrawlSnapshots.createdAt),
|
|
89413
|
+
desc40(siteCrawlSnapshots.runId)
|
|
89265
89414
|
).limit(10).all();
|
|
89266
89415
|
const runSelection = {
|
|
89267
89416
|
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",
|
|
@@ -89275,20 +89424,20 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89275
89424
|
};
|
|
89276
89425
|
const resolveAudit = (projectId, runId, recordedBefore) => {
|
|
89277
89426
|
const filters = [
|
|
89278
|
-
|
|
89279
|
-
|
|
89280
|
-
|
|
89281
|
-
|
|
89427
|
+
eq86(siteAuditSnapshots.projectId, projectId),
|
|
89428
|
+
eq86(runs.projectId, projectId),
|
|
89429
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
89430
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
89282
89431
|
notProbeRun(),
|
|
89283
89432
|
...recordedBefore ? [lt13(siteAuditSnapshots.createdAt, recordedBefore)] : []
|
|
89284
89433
|
];
|
|
89285
89434
|
const complete = sql30`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
|
|
89286
|
-
const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs,
|
|
89287
|
-
|
|
89288
|
-
|
|
89289
|
-
|
|
89290
|
-
|
|
89291
|
-
|
|
89435
|
+
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(
|
|
89436
|
+
desc40(sql30`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
|
|
89437
|
+
desc40(complete),
|
|
89438
|
+
desc40(siteAuditSnapshots.pagesAudited),
|
|
89439
|
+
desc40(siteAuditSnapshots.createdAt),
|
|
89440
|
+
desc40(siteAuditSnapshots.runId)
|
|
89292
89441
|
).limit(1).get();
|
|
89293
89442
|
const auditOfCrawl = (target) => {
|
|
89294
89443
|
const selected2 = target ? selectAudit(target.snapshot.runId) : void 0;
|
|
@@ -89304,22 +89453,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89304
89453
|
const date = selected.snap.createdAt.slice(0, 10);
|
|
89305
89454
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
89306
89455
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
89307
|
-
const sameDate =
|
|
89456
|
+
const sameDate = and72(
|
|
89308
89457
|
...filters,
|
|
89309
89458
|
gte22(siteAuditSnapshots.createdAt, date),
|
|
89310
89459
|
lt13(siteAuditSnapshots.createdAt, nextDate.toISOString())
|
|
89311
89460
|
);
|
|
89312
|
-
const sameDateRunCount = app.db.select({ value: count4() }).from(siteAuditSnapshots).innerJoin(runs,
|
|
89461
|
+
const sameDateRunCount = app.db.select({ value: count4() }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
89313
89462
|
const candidates = app.db.select({
|
|
89314
89463
|
runId: siteAuditSnapshots.runId,
|
|
89315
89464
|
createdAt: siteAuditSnapshots.createdAt,
|
|
89316
89465
|
complete,
|
|
89317
89466
|
pages: siteAuditSnapshots.pagesAudited
|
|
89318
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
89319
|
-
|
|
89320
|
-
|
|
89321
|
-
|
|
89322
|
-
|
|
89467
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
89468
|
+
desc40(complete),
|
|
89469
|
+
desc40(siteAuditSnapshots.pagesAudited),
|
|
89470
|
+
desc40(siteAuditSnapshots.createdAt),
|
|
89471
|
+
desc40(siteAuditSnapshots.runId)
|
|
89323
89472
|
).limit(10).all().map((candidate) => ({ ...candidate, complete: Boolean(candidate.complete) }));
|
|
89324
89473
|
const runSelection = {
|
|
89325
89474
|
// A crawl exists here only when none stored a scorecard: this score is a fallback.
|
|
@@ -89381,10 +89530,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89381
89530
|
};
|
|
89382
89531
|
const inventorySummaryFor = (scope) => {
|
|
89383
89532
|
if (!scope) return null;
|
|
89384
|
-
const groups = crawlReasonGroups(
|
|
89385
|
-
|
|
89386
|
-
|
|
89387
|
-
|
|
89533
|
+
const groups = crawlReasonGroups(and72(
|
|
89534
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
89535
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
89536
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId)
|
|
89388
89537
|
));
|
|
89389
89538
|
const total = groups.reduce((sum, group) => sum + group.pages, 0);
|
|
89390
89539
|
const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
|
|
@@ -89404,34 +89553,34 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89404
89553
|
const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
|
|
89405
89554
|
const pageInScope = (scope, selector) => {
|
|
89406
89555
|
if (!selector.nodeKey && !selector.url) return null;
|
|
89407
|
-
return app.db.select().from(siteCrawlPages).where(
|
|
89408
|
-
|
|
89409
|
-
|
|
89410
|
-
|
|
89411
|
-
selector.nodeKey ?
|
|
89556
|
+
return app.db.select().from(siteCrawlPages).where(and72(
|
|
89557
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
89558
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
89559
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
89560
|
+
selector.nodeKey ? eq86(siteCrawlPages.nodeKey, selector.nodeKey) : eq86(siteCrawlPages.url, selector.url)
|
|
89412
89561
|
)).orderBy(asc16(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
89413
89562
|
};
|
|
89414
|
-
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(
|
|
89415
|
-
|
|
89416
|
-
|
|
89417
|
-
|
|
89418
|
-
|
|
89563
|
+
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and72(
|
|
89564
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
89565
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
89566
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
89567
|
+
eq86(siteCrawlPages.depth, 0)
|
|
89419
89568
|
)).orderBy(asc16(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
89420
|
-
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(
|
|
89421
|
-
|
|
89422
|
-
|
|
89423
|
-
|
|
89424
|
-
|
|
89569
|
+
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and72(
|
|
89570
|
+
eq86(runs.id, runId),
|
|
89571
|
+
eq86(runs.projectId, projectId),
|
|
89572
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
89573
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
89425
89574
|
notProbeRun()
|
|
89426
89575
|
)).limit(1).get());
|
|
89427
89576
|
const assertKnownAuditRun = (projectId, runId) => {
|
|
89428
89577
|
if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
|
|
89429
89578
|
};
|
|
89430
|
-
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs,
|
|
89431
|
-
|
|
89432
|
-
|
|
89433
|
-
|
|
89434
|
-
|
|
89579
|
+
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(and72(
|
|
89580
|
+
eq86(siteAuditSnapshots.projectId, projectId),
|
|
89581
|
+
eq86(runs.projectId, projectId),
|
|
89582
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
89583
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
89435
89584
|
notProbeRun()
|
|
89436
89585
|
)).limit(1).get());
|
|
89437
89586
|
const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
|
|
@@ -89532,14 +89681,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89532
89681
|
};
|
|
89533
89682
|
}
|
|
89534
89683
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
89535
|
-
const conds = [
|
|
89536
|
-
if (statusFilter) conds.push(
|
|
89537
|
-
const where =
|
|
89684
|
+
const conds = [eq86(siteAuditPages.projectId, project.id), eq86(siteAuditPages.runId, latest.snap.runId)];
|
|
89685
|
+
if (statusFilter) conds.push(eq86(siteAuditPages.status, statusFilter));
|
|
89686
|
+
const where = and72(...conds);
|
|
89538
89687
|
const totalRow = app.db.select({ value: count4() }).from(siteAuditPages).where(where).get();
|
|
89539
89688
|
const total = totalRow?.value ?? 0;
|
|
89540
89689
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
89541
89690
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
89542
|
-
const orderBy = request.query.sort === "score-desc" ?
|
|
89691
|
+
const orderBy = request.query.sort === "score-desc" ? desc40(siteAuditPages.overallScore) : request.query.sort === "url" ? asc16(siteAuditPages.url) : asc16(siteAuditPages.overallScore);
|
|
89543
89692
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
89544
89693
|
const pages = rows.map((row) => ({
|
|
89545
89694
|
url: row.url,
|
|
@@ -89558,13 +89707,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89558
89707
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
89559
89708
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
89560
89709
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
89561
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
89562
|
-
|
|
89563
|
-
|
|
89564
|
-
|
|
89565
|
-
|
|
89710
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq86(siteAuditSnapshots.runId, runs.id)).where(and72(
|
|
89711
|
+
eq86(siteAuditSnapshots.projectId, project.id),
|
|
89712
|
+
eq86(runs.projectId, project.id),
|
|
89713
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
89714
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
89566
89715
|
notProbeRun()
|
|
89567
|
-
)).orderBy(
|
|
89716
|
+
)).orderBy(desc40(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
89568
89717
|
return { project: project.name, points: rows.reverse() };
|
|
89569
89718
|
});
|
|
89570
89719
|
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
@@ -89664,10 +89813,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89664
89813
|
{ projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
|
|
89665
89814
|
snapshot.rootUrl
|
|
89666
89815
|
)?.nodeKey ?? null;
|
|
89667
|
-
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(
|
|
89668
|
-
|
|
89669
|
-
|
|
89670
|
-
|
|
89816
|
+
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and72(
|
|
89817
|
+
eq86(siteCrawlGraphLayouts.projectId, project.id),
|
|
89818
|
+
eq86(siteCrawlGraphLayouts.runId, snapshot.runId),
|
|
89819
|
+
eq86(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
|
|
89671
89820
|
)).limit(1).get();
|
|
89672
89821
|
if (!persistedLayout) {
|
|
89673
89822
|
return {
|
|
@@ -89720,9 +89869,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89720
89869
|
const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
|
|
89721
89870
|
const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
|
|
89722
89871
|
const graphScope = [
|
|
89723
|
-
|
|
89724
|
-
|
|
89725
|
-
|
|
89872
|
+
eq86(siteCrawlGraphNodes.projectId, project.id),
|
|
89873
|
+
eq86(siteCrawlGraphNodes.runId, snapshot.runId),
|
|
89874
|
+
eq86(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
|
|
89726
89875
|
];
|
|
89727
89876
|
const nodeRows = app.db.select({
|
|
89728
89877
|
nodeKey: siteCrawlPages.nodeKey,
|
|
@@ -89741,18 +89890,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89741
89890
|
linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
|
|
89742
89891
|
x: siteCrawlGraphNodes.x,
|
|
89743
89892
|
y: siteCrawlGraphNodes.y
|
|
89744
|
-
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages,
|
|
89745
|
-
|
|
89746
|
-
|
|
89747
|
-
|
|
89748
|
-
|
|
89749
|
-
)).where(
|
|
89893
|
+
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and72(
|
|
89894
|
+
eq86(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
|
|
89895
|
+
eq86(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
|
|
89896
|
+
eq86(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
|
|
89897
|
+
eq86(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
|
|
89898
|
+
)).where(and72(...graphScope, lt13(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc16(siteCrawlGraphNodes.sampleRank)).all();
|
|
89750
89899
|
const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
|
|
89751
89900
|
...row,
|
|
89752
89901
|
healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
|
|
89753
89902
|
healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
|
|
89754
89903
|
}));
|
|
89755
|
-
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 :
|
|
89904
|
+
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq86(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
|
|
89756
89905
|
const edges = app.db.select({
|
|
89757
89906
|
edgeKey: siteCrawlGraphEdges.edgeKey,
|
|
89758
89907
|
sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
|
|
@@ -89760,22 +89909,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89760
89909
|
followable: siteCrawlGraphEdges.followable,
|
|
89761
89910
|
occurrences: siteCrawlGraphEdges.occurrences,
|
|
89762
89911
|
isTemplate: siteCrawlGraphEdges.isTemplate
|
|
89763
|
-
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode,
|
|
89764
|
-
|
|
89765
|
-
|
|
89766
|
-
|
|
89767
|
-
|
|
89912
|
+
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and72(
|
|
89913
|
+
eq86(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
|
|
89914
|
+
eq86(graphSourceNode.runId, siteCrawlGraphEdges.runId),
|
|
89915
|
+
eq86(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
89916
|
+
eq86(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
|
|
89768
89917
|
lt13(graphSourceNode.sampleRank, maxNodes)
|
|
89769
|
-
)).innerJoin(graphTargetNode,
|
|
89770
|
-
|
|
89771
|
-
|
|
89772
|
-
|
|
89773
|
-
|
|
89918
|
+
)).innerJoin(graphTargetNode, and72(
|
|
89919
|
+
eq86(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
|
|
89920
|
+
eq86(graphTargetNode.runId, siteCrawlGraphEdges.runId),
|
|
89921
|
+
eq86(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
89922
|
+
eq86(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
|
|
89774
89923
|
lt13(graphTargetNode.sampleRank, maxNodes)
|
|
89775
|
-
)).where(
|
|
89776
|
-
|
|
89777
|
-
|
|
89778
|
-
|
|
89924
|
+
)).where(and72(
|
|
89925
|
+
eq86(siteCrawlGraphEdges.projectId, project.id),
|
|
89926
|
+
eq86(siteCrawlGraphEdges.runId, snapshot.runId),
|
|
89927
|
+
eq86(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
|
|
89779
89928
|
lt13(siteCrawlGraphEdges.sampleRank, maxEdges),
|
|
89780
89929
|
graphLinkKindFilter
|
|
89781
89930
|
)).orderBy(asc16(siteCrawlGraphEdges.sampleRank)).all();
|
|
@@ -89870,15 +90019,15 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89870
90019
|
let frontier = [focus.nodeKey];
|
|
89871
90020
|
let queryTruncated = false;
|
|
89872
90021
|
for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
|
|
89873
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
89874
|
-
|
|
89875
|
-
|
|
89876
|
-
|
|
89877
|
-
|
|
89878
|
-
|
|
89879
|
-
|
|
89880
|
-
|
|
89881
|
-
|
|
90022
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and72(
|
|
90023
|
+
eq86(siteCrawlEdges.projectId, scope.projectId),
|
|
90024
|
+
eq86(siteCrawlEdges.runId, scope.runId),
|
|
90025
|
+
eq86(siteCrawlEdges.attemptId, scope.attemptId),
|
|
90026
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90027
|
+
isNotNull9(siteCrawlEdges.targetNodeKey),
|
|
90028
|
+
or21(
|
|
90029
|
+
inArray35(siteCrawlEdges.sourceNodeKey, frontier),
|
|
90030
|
+
inArray35(siteCrawlEdges.targetNodeKey, frontier)
|
|
89882
90031
|
)
|
|
89883
90032
|
)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
|
|
89884
90033
|
if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
|
|
@@ -89911,22 +90060,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
89911
90060
|
frontier = [...next].sort();
|
|
89912
90061
|
}
|
|
89913
90062
|
const nodeKeys = [...distances.keys()];
|
|
89914
|
-
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
89915
|
-
|
|
89916
|
-
|
|
89917
|
-
|
|
89918
|
-
|
|
90063
|
+
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and72(
|
|
90064
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
90065
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
90066
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
90067
|
+
inArray35(siteCrawlPages.nodeKey, nodeKeys)
|
|
89919
90068
|
)).all();
|
|
89920
90069
|
const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
|
|
89921
90070
|
const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
|
|
89922
|
-
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(
|
|
89923
|
-
|
|
89924
|
-
|
|
89925
|
-
|
|
89926
|
-
|
|
89927
|
-
|
|
89928
|
-
|
|
89929
|
-
|
|
90071
|
+
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and72(
|
|
90072
|
+
eq86(siteCrawlEdges.projectId, scope.projectId),
|
|
90073
|
+
eq86(siteCrawlEdges.runId, scope.runId),
|
|
90074
|
+
eq86(siteCrawlEdges.attemptId, scope.attemptId),
|
|
90075
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90076
|
+
isNotNull9(siteCrawlEdges.targetNodeKey),
|
|
90077
|
+
inArray35(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
|
|
90078
|
+
inArray35(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
|
|
89930
90079
|
)).limit(1).get();
|
|
89931
90080
|
if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
|
|
89932
90081
|
omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
|
|
@@ -90019,23 +90168,23 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90019
90168
|
let found = fromPage.nodeKey === toPage.nodeKey;
|
|
90020
90169
|
let truncated = false;
|
|
90021
90170
|
for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
|
|
90022
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
90023
|
-
|
|
90024
|
-
|
|
90025
|
-
|
|
90026
|
-
|
|
90027
|
-
|
|
90028
|
-
|
|
90029
|
-
|
|
90030
|
-
|
|
90171
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and72(
|
|
90172
|
+
eq86(siteCrawlEdges.projectId, scope.projectId),
|
|
90173
|
+
eq86(siteCrawlEdges.runId, scope.runId),
|
|
90174
|
+
eq86(siteCrawlEdges.attemptId, scope.attemptId),
|
|
90175
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90176
|
+
eq86(siteCrawlEdges.followable, true),
|
|
90177
|
+
eq86(siteCrawlEdges.relation, "anchor"),
|
|
90178
|
+
isNotNull9(siteCrawlEdges.targetNodeKey),
|
|
90179
|
+
inArray35(siteCrawlEdges.sourceNodeKey, frontier)
|
|
90031
90180
|
)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
|
|
90032
90181
|
if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
|
|
90033
90182
|
const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
|
|
90034
|
-
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(
|
|
90035
|
-
|
|
90036
|
-
|
|
90037
|
-
|
|
90038
|
-
|
|
90183
|
+
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and72(
|
|
90184
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
90185
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
90186
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
90187
|
+
inArray35(siteCrawlPages.nodeKey, candidateTargetKeys)
|
|
90039
90188
|
)).all().map((row) => row.nodeKey));
|
|
90040
90189
|
const next = /* @__PURE__ */ new Set();
|
|
90041
90190
|
for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
|
|
@@ -90076,11 +90225,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90076
90225
|
pathEdges.unshift(step.edge);
|
|
90077
90226
|
pathKeys.unshift(step.nodeKey);
|
|
90078
90227
|
}
|
|
90079
|
-
const pathRows = app.db.select().from(siteCrawlPages).where(
|
|
90080
|
-
|
|
90081
|
-
|
|
90082
|
-
|
|
90083
|
-
|
|
90228
|
+
const pathRows = app.db.select().from(siteCrawlPages).where(and72(
|
|
90229
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
90230
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
90231
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
90232
|
+
inArray35(siteCrawlPages.nodeKey, pathKeys)
|
|
90084
90233
|
)).all();
|
|
90085
90234
|
const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
|
|
90086
90235
|
const pathDetection = templateDetectionOf(snapshot.templateDetection);
|
|
@@ -90108,25 +90257,25 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90108
90257
|
throw validationError("change must be all, added, removed, or changed");
|
|
90109
90258
|
}
|
|
90110
90259
|
const completeSnapshotFilters = [
|
|
90111
|
-
|
|
90112
|
-
|
|
90113
|
-
|
|
90114
|
-
|
|
90115
|
-
|
|
90260
|
+
eq86(siteCrawlSnapshots.projectId, project.id),
|
|
90261
|
+
eq86(runs.projectId, project.id),
|
|
90262
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
90263
|
+
eq86(runs.status, RunStatuses.completed),
|
|
90264
|
+
eq86(siteCrawlSnapshots.complete, true),
|
|
90116
90265
|
notProbeRun()
|
|
90117
90266
|
];
|
|
90118
|
-
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
90267
|
+
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();
|
|
90119
90268
|
if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
|
|
90120
|
-
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
90269
|
+
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(
|
|
90121
90270
|
...completeSnapshotFilters,
|
|
90122
|
-
|
|
90271
|
+
or21(
|
|
90123
90272
|
lt13(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
90124
|
-
|
|
90125
|
-
|
|
90273
|
+
and72(
|
|
90274
|
+
eq86(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
90126
90275
|
lt13(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
|
|
90127
90276
|
)
|
|
90128
90277
|
)
|
|
90129
|
-
)).orderBy(
|
|
90278
|
+
)).orderBy(desc40(siteCrawlSnapshots.createdAt), desc40(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
|
|
90130
90279
|
if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
|
|
90131
90280
|
if (!afterTarget) {
|
|
90132
90281
|
const latestCrawl = resolveCrawl(project.id);
|
|
@@ -90367,18 +90516,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90367
90516
|
const visibleKeys = keyRows.slice(0, limit);
|
|
90368
90517
|
const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
|
|
90369
90518
|
const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
|
|
90370
|
-
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
90371
|
-
|
|
90372
|
-
|
|
90373
|
-
|
|
90374
|
-
|
|
90519
|
+
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and72(
|
|
90520
|
+
eq86(siteCrawlPages.projectId, scope.projectId),
|
|
90521
|
+
eq86(siteCrawlPages.runId, scope.runId),
|
|
90522
|
+
eq86(siteCrawlPages.attemptId, scope.attemptId),
|
|
90523
|
+
inArray35(siteCrawlPages.nodeKey, pageKeys)
|
|
90375
90524
|
)).all();
|
|
90376
|
-
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(
|
|
90377
|
-
|
|
90378
|
-
|
|
90379
|
-
|
|
90380
|
-
|
|
90381
|
-
|
|
90525
|
+
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and72(
|
|
90526
|
+
eq86(siteCrawlEdges.projectId, scope.projectId),
|
|
90527
|
+
eq86(siteCrawlEdges.runId, scope.runId),
|
|
90528
|
+
eq86(siteCrawlEdges.attemptId, scope.attemptId),
|
|
90529
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90530
|
+
inArray35(siteCrawlEdges.edgeKey, linkKeys)
|
|
90382
90531
|
)).all();
|
|
90383
90532
|
const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
90384
90533
|
const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
@@ -90531,16 +90680,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90531
90680
|
}
|
|
90532
90681
|
const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
|
|
90533
90682
|
const filters = [
|
|
90534
|
-
|
|
90535
|
-
|
|
90536
|
-
|
|
90683
|
+
eq86(siteCrawlPages.projectId, project.id),
|
|
90684
|
+
eq86(siteCrawlPages.runId, snapshot.runId),
|
|
90685
|
+
eq86(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
90537
90686
|
];
|
|
90538
|
-
if (request.query.nodeKey) filters.push(
|
|
90687
|
+
if (request.query.nodeKey) filters.push(eq86(siteCrawlPages.nodeKey, request.query.nodeKey));
|
|
90539
90688
|
const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
|
|
90540
|
-
if (inventoryEligible != null) filters.push(
|
|
90541
|
-
if (request.query.fetchState) filters.push(
|
|
90542
|
-
if (request.query.indexabilityState) filters.push(
|
|
90543
|
-
if (request.query.auditState) filters.push(
|
|
90689
|
+
if (inventoryEligible != null) filters.push(eq86(siteCrawlPages.inventoryEligible, inventoryEligible));
|
|
90690
|
+
if (request.query.fetchState) filters.push(eq86(siteCrawlPages.fetchState, request.query.fetchState));
|
|
90691
|
+
if (request.query.indexabilityState) filters.push(eq86(siteCrawlPages.indexabilityState, request.query.indexabilityState));
|
|
90692
|
+
if (request.query.auditState) filters.push(eq86(siteCrawlPages.auditState, request.query.auditState));
|
|
90544
90693
|
const healthState = parseSiteHealthState(request.query.healthState);
|
|
90545
90694
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
90546
90695
|
const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
|
|
@@ -90558,17 +90707,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90558
90707
|
sort
|
|
90559
90708
|
})).digest("base64url");
|
|
90560
90709
|
const offset = decodeCrawlPageCursor(request.query.cursor, selectionKey);
|
|
90561
|
-
const orderBy = sort === "score-desc" ? [
|
|
90710
|
+
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)];
|
|
90562
90711
|
let healthStateFilter = null;
|
|
90563
90712
|
if (healthState) {
|
|
90564
|
-
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(
|
|
90565
|
-
|
|
90566
|
-
|
|
90567
|
-
|
|
90713
|
+
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and72(
|
|
90714
|
+
eq86(siteCrawlPages.projectId, project.id),
|
|
90715
|
+
eq86(siteCrawlPages.runId, snapshot.runId),
|
|
90716
|
+
eq86(siteCrawlPages.attemptId, snapshot.attemptId),
|
|
90568
90717
|
isNull16(siteCrawlPages.healthState)
|
|
90569
90718
|
)).limit(1).get();
|
|
90570
90719
|
healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
|
|
90571
|
-
if (healthStateFilter === "applied") filters.push(
|
|
90720
|
+
if (healthStateFilter === "applied") filters.push(eq86(siteCrawlPages.healthState, healthState));
|
|
90572
90721
|
}
|
|
90573
90722
|
if (healthStateFilter === "unavailable-legacy-scan") {
|
|
90574
90723
|
return {
|
|
@@ -90585,7 +90734,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90585
90734
|
pages: []
|
|
90586
90735
|
};
|
|
90587
90736
|
}
|
|
90588
|
-
const where =
|
|
90737
|
+
const where = and72(...filters);
|
|
90589
90738
|
const total = app.db.select({ value: count4() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
|
|
90590
90739
|
const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
|
|
90591
90740
|
const nextOffset = offset + rows.length;
|
|
@@ -90620,10 +90769,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90620
90769
|
url: siteCrawlPages.url,
|
|
90621
90770
|
inventoryEligible: siteCrawlPages.inventoryEligible,
|
|
90622
90771
|
fetchState: siteCrawlPages.fetchState
|
|
90623
|
-
}).from(siteCrawlPages).where(
|
|
90624
|
-
|
|
90625
|
-
|
|
90626
|
-
|
|
90772
|
+
}).from(siteCrawlPages).where(and72(
|
|
90773
|
+
eq86(siteCrawlPages.projectId, project.id),
|
|
90774
|
+
eq86(siteCrawlPages.runId, snapshot.runId),
|
|
90775
|
+
eq86(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
90627
90776
|
)).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
|
|
90628
90777
|
if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
|
|
90629
90778
|
throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
|
|
@@ -90705,17 +90854,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90705
90854
|
};
|
|
90706
90855
|
}
|
|
90707
90856
|
const filters = [
|
|
90708
|
-
|
|
90709
|
-
|
|
90710
|
-
|
|
90711
|
-
|
|
90857
|
+
eq86(siteCrawlEdges.projectId, project.id),
|
|
90858
|
+
eq86(siteCrawlEdges.runId, snapshot.runId),
|
|
90859
|
+
eq86(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
90860
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90712
90861
|
linkKindFilter(linkKind)
|
|
90713
90862
|
];
|
|
90714
|
-
if (request.query.sourceUrl) filters.push(
|
|
90715
|
-
if (request.query.targetUrl) filters.push(
|
|
90863
|
+
if (request.query.sourceUrl) filters.push(eq86(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
|
|
90864
|
+
if (request.query.targetUrl) filters.push(eq86(siteCrawlEdges.targetUrl, request.query.targetUrl));
|
|
90716
90865
|
const followable = parseBoolean2(request.query.followable);
|
|
90717
|
-
if (followable != null) filters.push(
|
|
90718
|
-
const where =
|
|
90866
|
+
if (followable != null) filters.push(eq86(siteCrawlEdges.followable, followable));
|
|
90867
|
+
const where = and72(...filters);
|
|
90719
90868
|
const total = app.db.select({ value: count4() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
|
|
90720
90869
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
90721
90870
|
const offset = decodeCursor(request.query.cursor);
|
|
@@ -90776,17 +90925,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90776
90925
|
};
|
|
90777
90926
|
}
|
|
90778
90927
|
const scope = [
|
|
90779
|
-
|
|
90780
|
-
|
|
90781
|
-
|
|
90782
|
-
|
|
90928
|
+
eq86(siteCrawlEdges.projectId, project.id),
|
|
90929
|
+
eq86(siteCrawlEdges.runId, snapshot.runId),
|
|
90930
|
+
eq86(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
90931
|
+
eq86(siteCrawlEdges.internal, true),
|
|
90783
90932
|
linkKindFilter(linkKind)
|
|
90784
90933
|
];
|
|
90785
|
-
const inboundMatch = request.query.nodeKey && request.query.url ?
|
|
90786
|
-
const outboundMatch = request.query.nodeKey && request.query.url ?
|
|
90934
|
+
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);
|
|
90935
|
+
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);
|
|
90787
90936
|
const limit = parseBoundedLimit(request.query.limit, 50, 100);
|
|
90788
|
-
const inboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
90789
|
-
const outboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
90937
|
+
const inboundRows = app.db.select().from(siteCrawlEdges).where(and72(...scope, inboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
90938
|
+
const outboundRows = app.db.select().from(siteCrawlEdges).where(and72(...scope, outboundMatch)).orderBy(asc16(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
90790
90939
|
return {
|
|
90791
90940
|
project: project.name,
|
|
90792
90941
|
hasCrawlData: true,
|
|
@@ -90820,11 +90969,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90820
90969
|
if (!snapshot.attemptId) {
|
|
90821
90970
|
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 };
|
|
90822
90971
|
}
|
|
90823
|
-
const where =
|
|
90824
|
-
|
|
90825
|
-
|
|
90826
|
-
|
|
90827
|
-
|
|
90972
|
+
const where = and72(
|
|
90973
|
+
eq86(siteCrawlFindings.projectId, project.id),
|
|
90974
|
+
eq86(siteCrawlFindings.runId, snapshot.runId),
|
|
90975
|
+
eq86(siteCrawlFindings.attemptId, snapshot.attemptId),
|
|
90976
|
+
eq86(siteCrawlFindings.findingType, "dead-link"),
|
|
90828
90977
|
// Belt and braces on the client-facing surface: a row without a status
|
|
90829
90978
|
// code is not evidence of a broken link, and this response is where such
|
|
90830
90979
|
// a row would be read as one. Migration 140 removed every stored row that
|
|
@@ -90834,7 +90983,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90834
90983
|
// a fabricated broken link shown to a client is not worth the saved
|
|
90835
90984
|
// predicate. Applied to `total` as well as the rows, or the count would
|
|
90836
90985
|
// promise a page the list cannot fill.
|
|
90837
|
-
|
|
90986
|
+
isNotNull9(sql30`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
|
|
90838
90987
|
);
|
|
90839
90988
|
const total = app.db.select({ value: count4() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
|
|
90840
90989
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
@@ -90874,19 +91023,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90874
91023
|
createdAt: runs.createdAt,
|
|
90875
91024
|
startedAt: runs.startedAt,
|
|
90876
91025
|
finishedAt: runs.finishedAt
|
|
90877
|
-
}).from(runs).where(
|
|
90878
|
-
|
|
90879
|
-
|
|
91026
|
+
}).from(runs).where(and72(
|
|
91027
|
+
eq86(runs.projectId, project.id),
|
|
91028
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
90880
91029
|
notProbeRun()
|
|
90881
|
-
)).orderBy(
|
|
91030
|
+
)).orderBy(desc40(runs.createdAt), desc40(runs.id)).limit(limit).all();
|
|
90882
91031
|
const runIds = rows.map((row) => row.runId);
|
|
90883
|
-
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
90884
|
-
|
|
90885
|
-
|
|
90886
|
-
|
|
90887
|
-
|
|
91032
|
+
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq86(siteCrawlSnapshots.runId, runs.id)).where(and72(
|
|
91033
|
+
eq86(siteCrawlSnapshots.projectId, project.id),
|
|
91034
|
+
eq86(runs.projectId, project.id),
|
|
91035
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
91036
|
+
inArray35(runs.status, SURFACEABLE_STATUSES),
|
|
90888
91037
|
notProbeRun(),
|
|
90889
|
-
|
|
91038
|
+
inArray35(siteCrawlSnapshots.runId, runIds)
|
|
90890
91039
|
)).all().map((row) => row.runId));
|
|
90891
91040
|
return {
|
|
90892
91041
|
project: project.name,
|
|
@@ -90900,21 +91049,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90900
91049
|
});
|
|
90901
91050
|
app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
|
|
90902
91051
|
const project = resolveProject(app.db, request.params.name);
|
|
90903
|
-
const run = app.db.select().from(runs).where(
|
|
90904
|
-
|
|
90905
|
-
|
|
90906
|
-
|
|
91052
|
+
const run = app.db.select().from(runs).where(and72(
|
|
91053
|
+
eq86(runs.id, request.params.runId),
|
|
91054
|
+
eq86(runs.projectId, project.id),
|
|
91055
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
90907
91056
|
notProbeRun()
|
|
90908
91057
|
)).get();
|
|
90909
91058
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
90910
|
-
const attempt = app.db.select().from(siteCrawlAttempts).where(
|
|
90911
|
-
|
|
90912
|
-
|
|
90913
|
-
)).orderBy(
|
|
90914
|
-
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(
|
|
90915
|
-
|
|
90916
|
-
|
|
90917
|
-
|
|
91059
|
+
const attempt = app.db.select().from(siteCrawlAttempts).where(and72(
|
|
91060
|
+
eq86(siteCrawlAttempts.projectId, project.id),
|
|
91061
|
+
eq86(siteCrawlAttempts.runId, run.id)
|
|
91062
|
+
)).orderBy(desc40(siteCrawlAttempts.attemptNumber), desc40(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
91063
|
+
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and72(
|
|
91064
|
+
eq86(siteCrawlGraphLayouts.projectId, project.id),
|
|
91065
|
+
eq86(siteCrawlGraphLayouts.runId, run.id),
|
|
91066
|
+
eq86(siteCrawlGraphLayouts.attemptId, attempt.id)
|
|
90918
91067
|
)).get() : void 0;
|
|
90919
91068
|
const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
|
|
90920
91069
|
return {
|
|
@@ -90945,19 +91094,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90945
91094
|
};
|
|
90946
91095
|
});
|
|
90947
91096
|
app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
|
|
90948
|
-
const project = tx.select().from(projects).where(
|
|
91097
|
+
const project = tx.select().from(projects).where(eq86(projects.name, request.params.name)).get();
|
|
90949
91098
|
if (!project) throw notFound("Project", request.params.name);
|
|
90950
|
-
const run = tx.select().from(runs).where(
|
|
90951
|
-
|
|
90952
|
-
|
|
90953
|
-
|
|
91099
|
+
const run = tx.select().from(runs).where(and72(
|
|
91100
|
+
eq86(runs.id, request.params.runId),
|
|
91101
|
+
eq86(runs.projectId, project.id),
|
|
91102
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
90954
91103
|
notProbeRun()
|
|
90955
91104
|
)).get();
|
|
90956
91105
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
90957
|
-
const attempt = tx.select().from(siteCrawlAttempts).where(
|
|
90958
|
-
|
|
90959
|
-
|
|
90960
|
-
)).orderBy(
|
|
91106
|
+
const attempt = tx.select().from(siteCrawlAttempts).where(and72(
|
|
91107
|
+
eq86(siteCrawlAttempts.projectId, project.id),
|
|
91108
|
+
eq86(siteCrawlAttempts.runId, run.id)
|
|
91109
|
+
)).orderBy(desc40(siteCrawlAttempts.attemptNumber), desc40(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
90961
91110
|
const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
|
|
90962
91111
|
const base = {
|
|
90963
91112
|
project: project.name,
|
|
@@ -90970,11 +91119,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90970
91119
|
if (!attempt) {
|
|
90971
91120
|
return { ...base, pagesAudited: 0, examples: [] };
|
|
90972
91121
|
}
|
|
90973
|
-
const auditedWhere =
|
|
90974
|
-
|
|
90975
|
-
|
|
90976
|
-
|
|
90977
|
-
|
|
91122
|
+
const auditedWhere = and72(
|
|
91123
|
+
eq86(siteCrawlPages.projectId, project.id),
|
|
91124
|
+
eq86(siteCrawlPages.runId, run.id),
|
|
91125
|
+
eq86(siteCrawlPages.attemptId, attempt.id),
|
|
91126
|
+
eq86(siteCrawlPages.auditState, "success")
|
|
90978
91127
|
);
|
|
90979
91128
|
const pagesAudited = tx.select({ value: count4() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
|
|
90980
91129
|
if (state !== "collecting") {
|
|
@@ -90985,9 +91134,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
90985
91134
|
url: siteCrawlPages.url,
|
|
90986
91135
|
auditScore: siteCrawlPages.auditScore,
|
|
90987
91136
|
auditFields: siteCrawlPages.auditFields
|
|
90988
|
-
}).from(siteCrawlPages).where(
|
|
91137
|
+
}).from(siteCrawlPages).where(and72(
|
|
90989
91138
|
auditedWhere,
|
|
90990
|
-
|
|
91139
|
+
isNotNull9(siteCrawlPages.auditScore),
|
|
90991
91140
|
lt13(siteCrawlPages.auditScore, 70)
|
|
90992
91141
|
)).orderBy(asc16(siteCrawlPages.auditScore), asc16(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
|
|
90993
91142
|
const examples = candidates.flatMap((candidate) => {
|
|
@@ -91021,13 +91170,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91021
91170
|
status: runs.status,
|
|
91022
91171
|
identityKey: siteCrawlRunRequests.identityKey,
|
|
91023
91172
|
effectiveOptions: siteCrawlRunRequests.effectiveOptions
|
|
91024
|
-
}).from(runs).leftJoin(siteCrawlRunRequests,
|
|
91025
|
-
|
|
91026
|
-
|
|
91027
|
-
)).where(
|
|
91028
|
-
|
|
91029
|
-
|
|
91030
|
-
|
|
91173
|
+
}).from(runs).leftJoin(siteCrawlRunRequests, and72(
|
|
91174
|
+
eq86(siteCrawlRunRequests.projectId, runs.projectId),
|
|
91175
|
+
eq86(siteCrawlRunRequests.runId, runs.id)
|
|
91176
|
+
)).where(and72(
|
|
91177
|
+
eq86(runs.projectId, project.id),
|
|
91178
|
+
eq86(runs.kind, RunKinds["site-audit"]),
|
|
91179
|
+
inArray35(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
91031
91180
|
)).get();
|
|
91032
91181
|
if (existing) {
|
|
91033
91182
|
if (existing.identityKey === identityKey) {
|
|
@@ -91081,7 +91230,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
91081
91230
|
// ../api-routes/src/research.ts
|
|
91082
91231
|
import crypto53 from "crypto";
|
|
91083
91232
|
import { z as z5 } from "zod";
|
|
91084
|
-
import { and as
|
|
91233
|
+
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";
|
|
91085
91234
|
var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
|
|
91086
91235
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
91087
91236
|
async function researchRoutes(app, opts) {
|
|
@@ -91099,7 +91248,7 @@ async function researchRoutes(app, opts) {
|
|
|
91099
91248
|
const input = parsed.data;
|
|
91100
91249
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
91101
91250
|
const requestHash2 = directResearchRequestHash(input);
|
|
91102
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
91251
|
+
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and73(eq87(researchRuns.projectId, project.id), eq87(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
|
|
91103
91252
|
if (existingReceipt) {
|
|
91104
91253
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
91105
91254
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -91131,7 +91280,7 @@ async function researchRoutes(app, opts) {
|
|
|
91131
91280
|
const initiatedBy = researchPrincipal(request);
|
|
91132
91281
|
const decision = app.db.transaction((tx) => {
|
|
91133
91282
|
if (input.idempotencyKey) {
|
|
91134
|
-
const existing = tx.select().from(researchRuns).where(
|
|
91283
|
+
const existing = tx.select().from(researchRuns).where(and73(eq87(researchRuns.projectId, project.id), eq87(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
91135
91284
|
if (existing) {
|
|
91136
91285
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
91137
91286
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -91139,8 +91288,8 @@ async function researchRoutes(app, opts) {
|
|
|
91139
91288
|
}
|
|
91140
91289
|
if (initiatedBy?.limited) {
|
|
91141
91290
|
const { start, end, date } = utcDayBounds(now);
|
|
91142
|
-
const used = tx.select({ value: count5() }).from(researchRuns).where(
|
|
91143
|
-
|
|
91291
|
+
const used = tx.select({ value: count5() }).from(researchRuns).where(and73(
|
|
91292
|
+
eq87(researchRuns.projectId, project.id),
|
|
91144
91293
|
gte23(researchRuns.createdAt, start),
|
|
91145
91294
|
lt14(researchRuns.createdAt, end),
|
|
91146
91295
|
sql31`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -91171,15 +91320,15 @@ async function researchRoutes(app, opts) {
|
|
|
91171
91320
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
91172
91321
|
const requestHash2 = researchBatchRequestHash(input);
|
|
91173
91322
|
const receiptKeys = input.runs.map((_run, index2) => batchChildReceiptKey(input.idempotencyKey, index2));
|
|
91174
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
91175
|
-
|
|
91176
|
-
|
|
91323
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and73(
|
|
91324
|
+
eq87(researchRuns.projectId, project.id),
|
|
91325
|
+
eq87(researchRuns.idempotencyKey, receiptKeys[0])
|
|
91177
91326
|
)).get();
|
|
91178
91327
|
if (firstReceipt) {
|
|
91179
91328
|
if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
91180
|
-
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(
|
|
91181
|
-
|
|
91182
|
-
|
|
91329
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and73(
|
|
91330
|
+
eq87(researchRuns.projectId, project.id),
|
|
91331
|
+
eq87(researchRuns.idempotencyKey, key)
|
|
91183
91332
|
)).get());
|
|
91184
91333
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
91185
91334
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -91194,8 +91343,8 @@ async function researchRoutes(app, opts) {
|
|
|
91194
91343
|
const decision = app.db.transaction((tx) => {
|
|
91195
91344
|
if (initiatedBy?.limited) {
|
|
91196
91345
|
const { start, end, date } = utcDayBounds(now);
|
|
91197
|
-
const used = tx.select({ value: count5() }).from(researchRuns).where(
|
|
91198
|
-
|
|
91346
|
+
const used = tx.select({ value: count5() }).from(researchRuns).where(and73(
|
|
91347
|
+
eq87(researchRuns.projectId, project.id),
|
|
91199
91348
|
gte23(researchRuns.createdAt, start),
|
|
91200
91349
|
lt14(researchRuns.createdAt, end),
|
|
91201
91350
|
sql31`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -91249,10 +91398,10 @@ async function researchRoutes(app, opts) {
|
|
|
91249
91398
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
91250
91399
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
91251
91400
|
const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
|
|
91252
|
-
const rows = app.db.select().from(researchRuns).where(
|
|
91253
|
-
|
|
91254
|
-
cursor ?
|
|
91255
|
-
)).orderBy(
|
|
91401
|
+
const rows = app.db.select().from(researchRuns).where(and73(
|
|
91402
|
+
eq87(researchRuns.projectId, project.id),
|
|
91403
|
+
cursor ? or22(lt14(researchRuns.createdAt, cursor.createdAt), and73(eq87(researchRuns.createdAt, cursor.createdAt), lt14(researchRuns.id, cursor.id))) : void 0
|
|
91404
|
+
)).orderBy(desc41(researchRuns.createdAt), desc41(researchRuns.id)).limit(limit + 1).all();
|
|
91256
91405
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
91257
91406
|
const last = runs2.at(-1);
|
|
91258
91407
|
const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
|
|
@@ -91378,9 +91527,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
91378
91527
|
return stored;
|
|
91379
91528
|
}
|
|
91380
91529
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
91381
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
91382
|
-
|
|
91383
|
-
|
|
91530
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and73(
|
|
91531
|
+
eq87(measurementQueryTemplates.projectId, projectId),
|
|
91532
|
+
eq87(measurementQueryTemplates.id, selection.templateId)
|
|
91384
91533
|
)).get();
|
|
91385
91534
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
91386
91535
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -91389,7 +91538,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
91389
91538
|
}
|
|
91390
91539
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
91391
91540
|
if (bindingLocation) {
|
|
91392
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
91541
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq87(projects.id, projectId)).get();
|
|
91393
91542
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
91394
91543
|
}
|
|
91395
91544
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -91402,9 +91551,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
91402
91551
|
return queries2.map((query) => classifier?.classify(query) ?? null);
|
|
91403
91552
|
}
|
|
91404
91553
|
function getDetail(app, projectId, id) {
|
|
91405
|
-
const row = app.db.select().from(researchRuns).where(
|
|
91554
|
+
const row = app.db.select().from(researchRuns).where(and73(eq87(researchRuns.id, id), eq87(researchRuns.projectId, projectId))).get();
|
|
91406
91555
|
if (!row) throw notFound("Research run", id);
|
|
91407
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
91556
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq87(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
91408
91557
|
return { ...serializeRun(row), queries: queries2 };
|
|
91409
91558
|
}
|
|
91410
91559
|
function serializeRun(row) {
|
|
@@ -91434,7 +91583,7 @@ function utcDayBounds(now) {
|
|
|
91434
91583
|
|
|
91435
91584
|
// ../api-routes/src/snapshot-competitor-refresh.ts
|
|
91436
91585
|
import { setImmediate as yieldToEventLoop2 } from "timers/promises";
|
|
91437
|
-
import { and as
|
|
91586
|
+
import { and as and74, asc as asc17, eq as eq88, inArray as inArray36 } from "drizzle-orm";
|
|
91438
91587
|
var ANSWER_MENTIONS_CHUNK_SIZE = 50;
|
|
91439
91588
|
var ANSWER_MENTIONS_MAX_RESTARTS = 3;
|
|
91440
91589
|
function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
@@ -91484,14 +91633,14 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
|
|
|
91484
91633
|
examined = 0;
|
|
91485
91634
|
wouldUpdate = 0;
|
|
91486
91635
|
mentioned = 0;
|
|
91487
|
-
const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(
|
|
91488
|
-
|
|
91489
|
-
|
|
91490
|
-
opts?.runId === void 0 ? void 0 :
|
|
91636
|
+
const runRows = db.select({ id: runs.id, planVersionId: runs.measurementPlanVersionId }).from(runs).where(and74(
|
|
91637
|
+
eq88(runs.kind, RunKinds["answer-visibility"]),
|
|
91638
|
+
eq88(runs.projectId, projectId),
|
|
91639
|
+
opts?.runId === void 0 ? void 0 : eq88(runs.id, opts.runId)
|
|
91491
91640
|
)).all();
|
|
91492
91641
|
const planVersionByRun = new Map(runRows.map((run) => [run.id, run.planVersionId]));
|
|
91493
91642
|
for (const run of runRows) {
|
|
91494
|
-
const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
91643
|
+
const snapshotIds = db.select({ id: querySnapshots.id }).from(querySnapshots).where(eq88(querySnapshots.runId, run.id)).orderBy(asc17(querySnapshots.id)).all().map((row) => row.id);
|
|
91495
91644
|
for (let offset = 0; offset < snapshotIds.length; offset += ANSWER_MENTIONS_CHUNK_SIZE) {
|
|
91496
91645
|
if (chunks++ > 0 && driver.recheckIdentity) {
|
|
91497
91646
|
yield;
|
|
@@ -91517,7 +91666,7 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
|
|
|
91517
91666
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
91518
91667
|
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
91519
91668
|
rawResponse: querySnapshots.rawResponse
|
|
91520
|
-
}).from(querySnapshots).where(
|
|
91669
|
+
}).from(querySnapshots).where(inArray36(querySnapshots.id, snapshotIds.slice(offset, offset + ANSWER_MENTIONS_CHUNK_SIZE))).all();
|
|
91521
91670
|
const pendingUpdates = [];
|
|
91522
91671
|
for (const snapshot of snapshotRows) {
|
|
91523
91672
|
if (competitorFieldsOnly && snapshot.answerText == null) continue;
|
|
@@ -91567,7 +91716,7 @@ function* projectAnswerMentionsSteps(db, projectId, opts, driver) {
|
|
|
91567
91716
|
} else {
|
|
91568
91717
|
db.transaction((tx) => {
|
|
91569
91718
|
for (const update of pendingUpdates) {
|
|
91570
|
-
tx.update(querySnapshots).set(update.patch).where(
|
|
91719
|
+
tx.update(querySnapshots).set(update.patch).where(eq88(querySnapshots.id, update.id)).run();
|
|
91571
91720
|
}
|
|
91572
91721
|
});
|
|
91573
91722
|
updated += pendingUpdates.length;
|
|
@@ -91597,7 +91746,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
91597
91746
|
|
|
91598
91747
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
91599
91748
|
import crypto54 from "crypto";
|
|
91600
|
-
import { and as
|
|
91749
|
+
import { and as and75, eq as eq89 } from "drizzle-orm";
|
|
91601
91750
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
91602
91751
|
return db.transaction((tx) => {
|
|
91603
91752
|
const run = tx.select({
|
|
@@ -91605,7 +91754,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
91605
91754
|
trigger: runs.trigger,
|
|
91606
91755
|
status: runs.status,
|
|
91607
91756
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
91608
|
-
}).from(runs).where(
|
|
91757
|
+
}).from(runs).where(and75(eq89(runs.projectId, input.projectId), eq89(runs.id, input.runId))).get();
|
|
91609
91758
|
if (!run) throw notFound("Run", input.runId);
|
|
91610
91759
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
91611
91760
|
return null;
|
|
@@ -91626,7 +91775,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
91626
91775
|
throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
|
|
91627
91776
|
}
|
|
91628
91777
|
}
|
|
91629
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
91778
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and75(eq89(simpleMeasurementDefinitions.projectId, input.projectId), eq89(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
91630
91779
|
if (existing) {
|
|
91631
91780
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
91632
91781
|
const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition.identity;
|
|
@@ -91652,15 +91801,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
91652
91801
|
if (run.status !== RunStatuses.running) {
|
|
91653
91802
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
91654
91803
|
}
|
|
91655
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
91804
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq89(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
91656
91805
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
91657
91806
|
}
|
|
91658
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
91807
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq89(queries.projectId, input.projectId)).all().map((query) => query.id));
|
|
91659
91808
|
if (definition.queries.some((query) => !projectQueryIds.has(query.queryId))) {
|
|
91660
91809
|
throw validationError("Every captured query must belong to the run project.");
|
|
91661
91810
|
}
|
|
91662
91811
|
if (definition.competitors !== void 0) {
|
|
91663
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
91812
|
+
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));
|
|
91664
91813
|
const capturedDomains = new Set(definition.competitors.map((competitor) => competitor.domain));
|
|
91665
91814
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
91666
91815
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|
|
@@ -92528,10 +92677,10 @@ var log = createLogger("IntelligenceService");
|
|
|
92528
92677
|
var runChronologyKey = sql32`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
|
|
92529
92678
|
function analyzableRunPredicates(projectId) {
|
|
92530
92679
|
return [
|
|
92531
|
-
|
|
92680
|
+
eq90(runs.projectId, projectId),
|
|
92532
92681
|
// Only this kind writes query_snapshots.
|
|
92533
|
-
|
|
92534
|
-
|
|
92682
|
+
eq90(runs.kind, RunKinds["answer-visibility"]),
|
|
92683
|
+
or23(eq90(runs.status, RunStatuses.completed), eq90(runs.status, RunStatuses.partial)),
|
|
92535
92684
|
// Defensive: RunCoordinator already skips probes before analyzeAndPersist
|
|
92536
92685
|
// is called, but a future call site invoking it directly for a probe must
|
|
92537
92686
|
// still not pollute the intelligence window.
|
|
@@ -92576,16 +92725,16 @@ var IntelligenceService = class {
|
|
|
92576
92725
|
* Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
|
|
92577
92726
|
*/
|
|
92578
92727
|
analyzeAndPersist(runId, projectId) {
|
|
92579
|
-
const currentRunRecord = this.db.select().from(runs).where(
|
|
92728
|
+
const currentRunRecord = this.db.select().from(runs).where(and76(eq90(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
|
|
92580
92729
|
if (!currentRunRecord) {
|
|
92581
92730
|
log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
|
|
92582
92731
|
return null;
|
|
92583
92732
|
}
|
|
92584
92733
|
const currentLocation = currentRunRecord.location ?? null;
|
|
92585
92734
|
const recentRuns = this.db.select().from(runs).where(
|
|
92586
|
-
|
|
92735
|
+
and76(
|
|
92587
92736
|
...analyzableRunPredicates(projectId),
|
|
92588
|
-
currentLocation === null ? isNull17(runs.location) :
|
|
92737
|
+
currentLocation === null ? isNull17(runs.location) : eq90(runs.location, currentLocation),
|
|
92589
92738
|
// Never look forward. The window is this run's own past, so
|
|
92590
92739
|
// re-analyzing a historical run compares it against its true
|
|
92591
92740
|
// predecessor rather than against sweeps that came after it.
|
|
@@ -92593,10 +92742,10 @@ var IntelligenceService = class {
|
|
|
92593
92742
|
// MEASUREMENT: only runs that actually wrote snapshots — see above.
|
|
92594
92743
|
// Index seek on idx_snapshots_run, short-circuited on the first row.
|
|
92595
92744
|
exists2(
|
|
92596
|
-
this.db.select({ one: sql32`1` }).from(querySnapshots).where(
|
|
92745
|
+
this.db.select({ one: sql32`1` }).from(querySnapshots).where(eq90(querySnapshots.runId, runs.id))
|
|
92597
92746
|
)
|
|
92598
92747
|
)
|
|
92599
|
-
).orderBy(
|
|
92748
|
+
).orderBy(desc42(runs.finishedAt), desc42(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
|
|
92600
92749
|
const trackedCompetitors = this.loadTrackedCompetitors(projectId);
|
|
92601
92750
|
const trackedCompetitorDomains = trackedCompetitors.map((competitor) => competitor.domain);
|
|
92602
92751
|
const currentRun = this.buildRunData(
|
|
@@ -92669,7 +92818,7 @@ var IntelligenceService = class {
|
|
|
92669
92818
|
* Returns the persisted insights so the coordinator can count critical/high.
|
|
92670
92819
|
*/
|
|
92671
92820
|
analyzeAndPersistGbp(runId, projectId) {
|
|
92672
|
-
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
92821
|
+
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq90(runs.id, runId)).get();
|
|
92673
92822
|
if (!runRow) {
|
|
92674
92823
|
log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
|
|
92675
92824
|
this.persistGbpInsights(runId, projectId, [], []);
|
|
@@ -92677,9 +92826,9 @@ var IntelligenceService = class {
|
|
|
92677
92826
|
}
|
|
92678
92827
|
const windowStart = runRow.startedAt ?? runRow.createdAt;
|
|
92679
92828
|
const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
92680
|
-
const selected = this.db.select().from(gbpLocations).where(
|
|
92681
|
-
|
|
92682
|
-
|
|
92829
|
+
const selected = this.db.select().from(gbpLocations).where(and76(
|
|
92830
|
+
eq90(gbpLocations.projectId, projectId),
|
|
92831
|
+
eq90(gbpLocations.selected, true),
|
|
92683
92832
|
gte24(gbpLocations.syncedAt, windowStart),
|
|
92684
92833
|
lte20(gbpLocations.syncedAt, windowEnd)
|
|
92685
92834
|
)).all();
|
|
@@ -92714,12 +92863,12 @@ var IntelligenceService = class {
|
|
|
92714
92863
|
}
|
|
92715
92864
|
/** Build the per-location signal bundle the GBP analyzer consumes. */
|
|
92716
92865
|
buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
|
|
92717
|
-
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(
|
|
92718
|
-
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(
|
|
92719
|
-
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(
|
|
92720
|
-
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(
|
|
92866
|
+
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();
|
|
92867
|
+
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and76(eq90(gbpPlaceActions.projectId, projectId), eq90(gbpPlaceActions.locationName, locationName))).all();
|
|
92868
|
+
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();
|
|
92869
|
+
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and76(eq90(gbpLocations.projectId, projectId), eq90(gbpLocations.locationName, locationName))).get();
|
|
92721
92870
|
const descriptionMissing = !(ownerRow?.description ?? "").trim();
|
|
92722
|
-
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(
|
|
92871
|
+
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();
|
|
92723
92872
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
92724
92873
|
const summary3 = buildGbpSummary({
|
|
92725
92874
|
locationName,
|
|
@@ -92755,7 +92904,7 @@ var IntelligenceService = class {
|
|
|
92755
92904
|
/** Build the month-over-month keyword series for a location from the
|
|
92756
92905
|
* accumulating gbp_keyword_monthly table (latest complete month vs prior). */
|
|
92757
92906
|
buildGbpKeywordTrend(projectId, locationName) {
|
|
92758
|
-
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(
|
|
92907
|
+
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();
|
|
92759
92908
|
if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
|
|
92760
92909
|
const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
|
|
92761
92910
|
const recentMonth = months[0] ?? null;
|
|
@@ -92786,7 +92935,7 @@ var IntelligenceService = class {
|
|
|
92786
92935
|
*/
|
|
92787
92936
|
persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
|
|
92788
92937
|
const covered = new Set(coveredLocationNames);
|
|
92789
|
-
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(
|
|
92938
|
+
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();
|
|
92790
92939
|
const staleIds = [];
|
|
92791
92940
|
const dismissedSlots = /* @__PURE__ */ new Set();
|
|
92792
92941
|
for (const row of existing) {
|
|
@@ -92797,7 +92946,7 @@ var IntelligenceService = class {
|
|
|
92797
92946
|
}
|
|
92798
92947
|
this.db.transaction((tx) => {
|
|
92799
92948
|
for (const id of staleIds) {
|
|
92800
|
-
tx.delete(insights).where(
|
|
92949
|
+
tx.delete(insights).where(eq90(insights.id, id)).run();
|
|
92801
92950
|
}
|
|
92802
92951
|
for (const insight of gbpInsights) {
|
|
92803
92952
|
const parsed = parseGbpInsightId(insight.id);
|
|
@@ -92884,7 +93033,7 @@ var IntelligenceService = class {
|
|
|
92884
93033
|
* create per run + aggregate). DB is left untouched.
|
|
92885
93034
|
*/
|
|
92886
93035
|
backfill(projectName, opts, onProgress) {
|
|
92887
|
-
const project = this.db.select().from(projects).where(
|
|
93036
|
+
const project = this.db.select().from(projects).where(eq90(projects.name, projectName)).get();
|
|
92888
93037
|
if (!project) {
|
|
92889
93038
|
throw new Error(`Project "${projectName}" not found`);
|
|
92890
93039
|
}
|
|
@@ -92896,7 +93045,7 @@ var IntelligenceService = class {
|
|
|
92896
93045
|
}
|
|
92897
93046
|
sinceTimestamp = parsed;
|
|
92898
93047
|
}
|
|
92899
|
-
const allRuns = this.db.select().from(runs).where(
|
|
93048
|
+
const allRuns = this.db.select().from(runs).where(and76(...analyzableRunPredicates(project.id))).orderBy(asc18(runs.finishedAt)).all();
|
|
92900
93049
|
const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
|
|
92901
93050
|
const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
|
|
92902
93051
|
let startIdx = 0;
|
|
@@ -92927,7 +93076,7 @@ var IntelligenceService = class {
|
|
|
92927
93076
|
let wouldDeleteTotal = 0;
|
|
92928
93077
|
const existingByRunId = /* @__PURE__ */ new Map();
|
|
92929
93078
|
if (isDryRun && targetRuns.length > 0) {
|
|
92930
|
-
const rows = this.db.select({ runId: insights.runId }).from(insights).where(
|
|
93079
|
+
const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray37(insights.runId, targetRuns.map((r) => r.id))).all();
|
|
92931
93080
|
for (const r of rows) {
|
|
92932
93081
|
if (r.runId == null) continue;
|
|
92933
93082
|
existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
|
|
@@ -92978,7 +93127,7 @@ var IntelligenceService = class {
|
|
|
92978
93127
|
* signals match the same identities the dashboard and reports do.
|
|
92979
93128
|
*/
|
|
92980
93129
|
loadTrackedCompetitors(projectId) {
|
|
92981
|
-
return this.db.select(competitorIdentityColumns).from(competitors).where(
|
|
93130
|
+
return this.db.select(competitorIdentityColumns).from(competitors).where(eq90(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
92982
93131
|
}
|
|
92983
93132
|
/**
|
|
92984
93133
|
* Which of `runIds` actually wrote query snapshots. One grouped read, so
|
|
@@ -92987,7 +93136,7 @@ var IntelligenceService = class {
|
|
|
92987
93136
|
*/
|
|
92988
93137
|
runIdsWithSnapshots(runIds) {
|
|
92989
93138
|
if (runIds.length === 0) return /* @__PURE__ */ new Set();
|
|
92990
|
-
const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(
|
|
93139
|
+
const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray37(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
|
|
92991
93140
|
return new Set(rows.map((r) => r.runId));
|
|
92992
93141
|
}
|
|
92993
93142
|
/**
|
|
@@ -93009,15 +93158,15 @@ var IntelligenceService = class {
|
|
|
93009
93158
|
}
|
|
93010
93159
|
persistResult(result, runId, projectId) {
|
|
93011
93160
|
const previouslyDismissed = /* @__PURE__ */ new Set();
|
|
93012
|
-
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(
|
|
93161
|
+
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();
|
|
93013
93162
|
for (const row of existingInsights) {
|
|
93014
93163
|
if (row.dismissed) {
|
|
93015
93164
|
previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
|
|
93016
93165
|
}
|
|
93017
93166
|
}
|
|
93018
93167
|
this.db.transaction((tx) => {
|
|
93019
|
-
tx.delete(insights).where(
|
|
93020
|
-
tx.delete(healthSnapshots).where(
|
|
93168
|
+
tx.delete(insights).where(eq90(insights.runId, runId)).run();
|
|
93169
|
+
tx.delete(healthSnapshots).where(eq90(healthSnapshots.runId, runId)).run();
|
|
93021
93170
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
93022
93171
|
for (const insight of result.insights) {
|
|
93023
93172
|
const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
|
|
@@ -93064,8 +93213,8 @@ var IntelligenceService = class {
|
|
|
93064
93213
|
clicks: gscSearchData.clicks,
|
|
93065
93214
|
impressions: gscSearchData.impressions,
|
|
93066
93215
|
position: gscSearchData.position
|
|
93067
|
-
}).from(gscSearchData).where(
|
|
93068
|
-
|
|
93216
|
+
}).from(gscSearchData).where(and76(
|
|
93217
|
+
eq90(gscSearchData.projectId, projectId),
|
|
93069
93218
|
gte24(gscSearchData.date, window.startDate),
|
|
93070
93219
|
lte20(gscSearchData.date, window.endDate)
|
|
93071
93220
|
)).all();
|
|
@@ -93118,21 +93267,21 @@ var IntelligenceService = class {
|
|
|
93118
93267
|
const key = row.query.toLowerCase();
|
|
93119
93268
|
gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
|
|
93120
93269
|
}
|
|
93121
|
-
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
93270
|
+
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq90(projects.id, projectId)).get();
|
|
93122
93271
|
const locationCount = Math.max(
|
|
93123
93272
|
1,
|
|
93124
93273
|
(projectRow?.locations ?? []).length
|
|
93125
93274
|
);
|
|
93126
93275
|
const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
|
|
93127
93276
|
const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
93128
|
-
|
|
93129
|
-
|
|
93130
|
-
|
|
93131
|
-
|
|
93277
|
+
and76(
|
|
93278
|
+
eq90(runs.projectId, projectId),
|
|
93279
|
+
eq90(runs.kind, RunKinds["answer-visibility"]),
|
|
93280
|
+
or23(eq90(runs.status, "completed"), eq90(runs.status, "partial")),
|
|
93132
93281
|
// Defensive — see top of file.
|
|
93133
93282
|
ne16(runs.trigger, RunTriggers.probe)
|
|
93134
93283
|
)
|
|
93135
|
-
).orderBy(
|
|
93284
|
+
).orderBy(desc42(runs.createdAt), desc42(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
|
|
93136
93285
|
const recentGroups = groupRunsByCreatedAt(recentRunRows);
|
|
93137
93286
|
const recentRunIds = [];
|
|
93138
93287
|
const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
|
|
@@ -93148,7 +93297,7 @@ var IntelligenceService = class {
|
|
|
93148
93297
|
const haveHistory = recentRunIds.length > 0;
|
|
93149
93298
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
93150
93299
|
if (haveHistory) {
|
|
93151
|
-
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(
|
|
93300
|
+
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();
|
|
93152
93301
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
93153
93302
|
for (const row of priorRows) {
|
|
93154
93303
|
if (!row.runId) continue;
|
|
@@ -93177,7 +93326,7 @@ var IntelligenceService = class {
|
|
|
93177
93326
|
});
|
|
93178
93327
|
}
|
|
93179
93328
|
buildRunData(runId, projectId, completedAt, location = null, trackedCompetitors = []) {
|
|
93180
|
-
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(
|
|
93329
|
+
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq90(projects.id, projectId)).get();
|
|
93181
93330
|
const projectDomains = projectDomainRow ? effectiveDomains({
|
|
93182
93331
|
canonicalDomain: projectDomainRow.canonicalDomain,
|
|
93183
93332
|
ownedDomains: projectDomainRow.ownedDomains
|
|
@@ -93195,7 +93344,7 @@ var IntelligenceService = class {
|
|
|
93195
93344
|
citedDomains: querySnapshots.citedDomains,
|
|
93196
93345
|
rawResponse: querySnapshots.rawResponse,
|
|
93197
93346
|
snapshotLocation: querySnapshots.location
|
|
93198
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
93347
|
+
}).from(querySnapshots).leftJoin(queries, eq90(querySnapshots.queryId, queries.id)).where(eq90(querySnapshots.runId, runId)).all();
|
|
93199
93348
|
const snapshots = [];
|
|
93200
93349
|
let orphanCount = 0;
|
|
93201
93350
|
for (const r of rows) {
|