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