@ainyc/canonry 4.84.0 → 4.85.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/aero/references/regression-playbook.md +2 -0
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +19 -0
- package/assets/assets/{BacklinksPage-OrSg_iPA.js → BacklinksPage-CDAv0ggn.js} +1 -1
- package/assets/assets/{ChartPrimitives-DPBhAT_r.js → ChartPrimitives-CnAmsyt7.js} +1 -1
- package/assets/assets/{ProjectPage-CpMcEmtw.js → ProjectPage-C9KEgRxD.js} +1 -1
- package/assets/assets/{RunRow-2Rty0BAH.js → RunRow-CVZ5o8fg.js} +1 -1
- package/assets/assets/{RunsPage-B3ahqf8s.js → RunsPage-Bzy5c0MZ.js} +1 -1
- package/assets/assets/{SettingsPage-BIjeI85q.js → SettingsPage-B1ocxPBe.js} +1 -1
- package/assets/assets/{TrafficPage-DjGoj691.js → TrafficPage-D2zepQOC.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BgKG-2q3.js → TrafficSourceDetailPage-C7JuAkaK.js} +1 -1
- package/assets/assets/{arrow-left-Cf7wmru1.js → arrow-left-Bv3CWylm.js} +1 -1
- package/assets/assets/{extract-error-message-CANxezte.js → extract-error-message-BtVid5TP.js} +1 -1
- package/assets/assets/{index-CGlPx_cu.js → index-DmNti_xn.js} +72 -72
- package/assets/assets/{trash-2-6nHJZrvy.js → trash-2-BoimCsYz.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-VJBO4VIK.js → chunk-3K3QRSYE.js} +620 -422
- package/dist/{chunk-Y3O3HBMN.js → chunk-62YB3ML7.js} +49 -1
- package/dist/{chunk-M3IYKTSF.js → chunk-7BMSWI2K.js} +4 -4
- package/dist/{chunk-BNF3HXBW.js → chunk-I2BJC3DT.js} +1021 -942
- package/dist/cli.js +151 -20
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-PDIAMP5I.js → intelligence-service-AHHBQKRD.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +9 -9
|
@@ -169,6 +169,7 @@ import {
|
|
|
169
169
|
notFound,
|
|
170
170
|
notImplemented,
|
|
171
171
|
notificationDtoSchema,
|
|
172
|
+
parseInclusiveEndMs,
|
|
172
173
|
parseRunError,
|
|
173
174
|
parseWindow,
|
|
174
175
|
pickClusterRepresentative,
|
|
@@ -230,6 +231,7 @@ import {
|
|
|
230
231
|
unsupportedKind,
|
|
231
232
|
validationError,
|
|
232
233
|
visibilityStateFromAnswerMentioned,
|
|
234
|
+
visibilityStatsDtoSchema,
|
|
233
235
|
windowCutoff,
|
|
234
236
|
winnabilityClassLabel,
|
|
235
237
|
winnabilityClassSchema,
|
|
@@ -246,10 +248,10 @@ import {
|
|
|
246
248
|
wordpressSchemaDeployResultDtoSchema,
|
|
247
249
|
wordpressSchemaStatusResultDtoSchema,
|
|
248
250
|
wordpressStatusDtoSchema
|
|
249
|
-
} from "./chunk-
|
|
251
|
+
} from "./chunk-I2BJC3DT.js";
|
|
250
252
|
|
|
251
253
|
// src/intelligence-service.ts
|
|
252
|
-
import { eq as
|
|
254
|
+
import { eq as eq37, desc as desc18, asc as asc5, and as and27, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
|
|
253
255
|
|
|
254
256
|
// ../db/src/client.ts
|
|
255
257
|
import { mkdirSync } from "fs";
|
|
@@ -4339,18 +4341,18 @@ function buildCitationScorecard(snapshots, queryLookup) {
|
|
|
4339
4341
|
answerMentioned: snap.answerMentioned ?? null,
|
|
4340
4342
|
model: snap.model
|
|
4341
4343
|
};
|
|
4342
|
-
const
|
|
4343
|
-
|
|
4344
|
-
if (snap.citationState === CitationStates.cited)
|
|
4345
|
-
providerCounts.set(snap.provider,
|
|
4344
|
+
const counts2 = providerCounts.get(snap.provider) ?? { cited: 0, total: 0 };
|
|
4345
|
+
counts2.total++;
|
|
4346
|
+
if (snap.citationState === CitationStates.cited) counts2.cited++;
|
|
4347
|
+
providerCounts.set(snap.provider, counts2);
|
|
4346
4348
|
}
|
|
4347
4349
|
const providerRates = providerList.map((provider) => {
|
|
4348
|
-
const
|
|
4349
|
-
const citationRate =
|
|
4350
|
+
const counts2 = providerCounts.get(provider) ?? { cited: 0, total: 0 };
|
|
4351
|
+
const citationRate = counts2.total > 0 ? Math.round(counts2.cited / counts2.total * 100) : 0;
|
|
4350
4352
|
return {
|
|
4351
4353
|
provider,
|
|
4352
|
-
citedCount:
|
|
4353
|
-
totalCount:
|
|
4354
|
+
citedCount: counts2.cited,
|
|
4355
|
+
totalCount: counts2.total,
|
|
4354
4356
|
citationRate
|
|
4355
4357
|
};
|
|
4356
4358
|
});
|
|
@@ -7918,13 +7920,13 @@ function buildRankedList(domains, limit) {
|
|
|
7918
7920
|
bySurfaceClass
|
|
7919
7921
|
};
|
|
7920
7922
|
}
|
|
7921
|
-
function buildCategoryCounts(
|
|
7923
|
+
function buildCategoryCounts(counts2) {
|
|
7922
7924
|
let grandTotal = 0;
|
|
7923
|
-
for (const domains of
|
|
7925
|
+
for (const domains of counts2.values()) {
|
|
7924
7926
|
for (const count2 of domains.values()) grandTotal += count2;
|
|
7925
7927
|
}
|
|
7926
7928
|
const result = [];
|
|
7927
|
-
for (const [category, domains] of
|
|
7929
|
+
for (const [category, domains] of counts2) {
|
|
7928
7930
|
let categoryTotal = 0;
|
|
7929
7931
|
const domainEntries = [];
|
|
7930
7932
|
for (const [domain, count2] of domains) {
|
|
@@ -11738,19 +11740,19 @@ function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
|
|
|
11738
11740
|
considered++;
|
|
11739
11741
|
if (snap.citationState === CitationStates.cited) citedQueryIds.add(snap.queryId);
|
|
11740
11742
|
if (snap.answerMentioned) mentionedQueryIds.add(snap.queryId);
|
|
11741
|
-
const
|
|
11742
|
-
|
|
11743
|
-
if (snap.citationState === CitationStates.cited)
|
|
11744
|
-
providerCounts.set(snap.provider,
|
|
11743
|
+
const counts2 = providerCounts.get(snap.provider) ?? { cited: 0, total: 0 };
|
|
11744
|
+
counts2.total++;
|
|
11745
|
+
if (snap.citationState === CitationStates.cited) counts2.cited++;
|
|
11746
|
+
providerCounts.set(snap.provider, counts2);
|
|
11745
11747
|
}
|
|
11746
11748
|
if (considered === 0) continue;
|
|
11747
11749
|
const citedQueryCount = citedQueryIds.size;
|
|
11748
11750
|
const mentionedQueryCount = mentionedQueryIds.size;
|
|
11749
11751
|
const citationRate = totalQueries > 0 ? Math.round(citedQueryCount / totalQueries * 100) : 0;
|
|
11750
11752
|
const mentionRate = totalQueries > 0 ? Math.round(mentionedQueryCount / totalQueries * 100) : 0;
|
|
11751
|
-
const providerRates = [...providerCounts.entries()].map(([provider,
|
|
11753
|
+
const providerRates = [...providerCounts.entries()].map(([provider, counts2]) => ({
|
|
11752
11754
|
provider,
|
|
11753
|
-
citationRate:
|
|
11755
|
+
citationRate: counts2.total > 0 ? Math.round(counts2.cited / counts2.total * 100) : 0
|
|
11754
11756
|
})).sort((a, b) => a.provider.localeCompare(b.provider));
|
|
11755
11757
|
points.push({
|
|
11756
11758
|
runId: run.id,
|
|
@@ -12720,8 +12722,165 @@ function normalizeDomain2(domain) {
|
|
|
12720
12722
|
return domain.toLowerCase().trim().replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/$/, "");
|
|
12721
12723
|
}
|
|
12722
12724
|
|
|
12725
|
+
// ../api-routes/src/visibility-stats.ts
|
|
12726
|
+
import { and as and11, desc as desc8, eq as eq16, inArray as inArray8 } from "drizzle-orm";
|
|
12727
|
+
function round42(value) {
|
|
12728
|
+
return Math.round(value * 1e4) / 1e4;
|
|
12729
|
+
}
|
|
12730
|
+
function emptyAgg() {
|
|
12731
|
+
return { total: 0, checked: 0, mentioned: 0, cited: 0, first: null, last: null };
|
|
12732
|
+
}
|
|
12733
|
+
function addSnapshot(agg, snap) {
|
|
12734
|
+
agg.total++;
|
|
12735
|
+
if (snap.answerMentioned === true || snap.answerMentioned === false) agg.checked++;
|
|
12736
|
+
if (snap.answerMentioned === true) agg.mentioned++;
|
|
12737
|
+
if (snap.citationState === CitationStates.cited) agg.cited++;
|
|
12738
|
+
if (agg.first === null || snap.createdAt < agg.first) agg.first = snap.createdAt;
|
|
12739
|
+
if (agg.last === null || snap.createdAt > agg.last) agg.last = snap.createdAt;
|
|
12740
|
+
}
|
|
12741
|
+
function counts(agg) {
|
|
12742
|
+
return {
|
|
12743
|
+
total: agg.total,
|
|
12744
|
+
checked: agg.checked,
|
|
12745
|
+
mentioned: agg.mentioned,
|
|
12746
|
+
cited: agg.cited,
|
|
12747
|
+
// mention proportion is over the CHECKED sample; citation proportion is
|
|
12748
|
+
// over the full total (every snapshot is checked for citation).
|
|
12749
|
+
mentionRate: agg.checked > 0 ? round42(agg.mentioned / agg.checked) : null,
|
|
12750
|
+
citedRate: agg.total > 0 ? round42(agg.cited / agg.total) : null
|
|
12751
|
+
};
|
|
12752
|
+
}
|
|
12753
|
+
function providerEntries(byProvider) {
|
|
12754
|
+
return [...byProvider.entries()].map(([provider, agg]) => ({
|
|
12755
|
+
provider,
|
|
12756
|
+
...counts(agg),
|
|
12757
|
+
// first/last are non-null once at least one snapshot landed in the agg,
|
|
12758
|
+
// which is guaranteed for any provider that made it into the map.
|
|
12759
|
+
firstObserved: agg.first ?? "",
|
|
12760
|
+
lastObserved: agg.last ?? ""
|
|
12761
|
+
})).sort((a, b) => a.provider.localeCompare(b.provider));
|
|
12762
|
+
}
|
|
12763
|
+
function computeVisibilityStats(input) {
|
|
12764
|
+
const { groupBy } = input;
|
|
12765
|
+
const wantProviders = groupBy === "provider";
|
|
12766
|
+
const queryById = /* @__PURE__ */ new Map();
|
|
12767
|
+
const queryByText = /* @__PURE__ */ new Map();
|
|
12768
|
+
for (const q of input.queries) {
|
|
12769
|
+
queryById.set(q.id, q);
|
|
12770
|
+
queryByText.set(q.query, q);
|
|
12771
|
+
}
|
|
12772
|
+
const byQuery = /* @__PURE__ */ new Map();
|
|
12773
|
+
const totals = emptyAgg();
|
|
12774
|
+
const totalsByProvider = /* @__PURE__ */ new Map();
|
|
12775
|
+
for (const snap of input.snapshots) {
|
|
12776
|
+
let resolved;
|
|
12777
|
+
if (snap.queryId && queryById.has(snap.queryId)) resolved = queryById.get(snap.queryId);
|
|
12778
|
+
else if (snap.queryText && queryByText.has(snap.queryText)) resolved = queryByText.get(snap.queryText);
|
|
12779
|
+
if (!resolved) continue;
|
|
12780
|
+
let bucket = byQuery.get(resolved.id);
|
|
12781
|
+
if (!bucket) {
|
|
12782
|
+
bucket = { id: resolved.id, query: resolved.query, agg: emptyAgg(), byProvider: /* @__PURE__ */ new Map() };
|
|
12783
|
+
byQuery.set(resolved.id, bucket);
|
|
12784
|
+
}
|
|
12785
|
+
addSnapshot(bucket.agg, snap);
|
|
12786
|
+
addSnapshot(totals, snap);
|
|
12787
|
+
if (wantProviders) {
|
|
12788
|
+
const qpAgg = bucket.byProvider.get(snap.provider) ?? emptyAgg();
|
|
12789
|
+
addSnapshot(qpAgg, snap);
|
|
12790
|
+
bucket.byProvider.set(snap.provider, qpAgg);
|
|
12791
|
+
const tpAgg = totalsByProvider.get(snap.provider) ?? emptyAgg();
|
|
12792
|
+
addSnapshot(tpAgg, snap);
|
|
12793
|
+
totalsByProvider.set(snap.provider, tpAgg);
|
|
12794
|
+
}
|
|
12795
|
+
}
|
|
12796
|
+
const queryEntries = [...byQuery.values()].map((bucket) => ({
|
|
12797
|
+
queryId: bucket.id,
|
|
12798
|
+
query: bucket.query,
|
|
12799
|
+
...counts(bucket.agg),
|
|
12800
|
+
firstObserved: bucket.agg.first ?? "",
|
|
12801
|
+
lastObserved: bucket.agg.last ?? "",
|
|
12802
|
+
...wantProviders ? { providers: providerEntries(bucket.byProvider) } : {}
|
|
12803
|
+
})).sort((a, b) => a.query.localeCompare(b.query));
|
|
12804
|
+
return {
|
|
12805
|
+
totals: counts(totals),
|
|
12806
|
+
...wantProviders ? { byProvider: providerEntries(totalsByProvider) } : {},
|
|
12807
|
+
queries: queryEntries
|
|
12808
|
+
};
|
|
12809
|
+
}
|
|
12810
|
+
async function visibilityStatsRoutes(app) {
|
|
12811
|
+
app.get("/projects/:name/visibility-stats", async (request, reply) => {
|
|
12812
|
+
const project = resolveProject(app.db, request.params.name);
|
|
12813
|
+
const { since: sinceRaw, until: untilRaw, lastRuns: lastRunsRaw, groupBy: groupByRaw } = request.query;
|
|
12814
|
+
let groupBy = null;
|
|
12815
|
+
if (groupByRaw !== void 0 && groupByRaw !== "") {
|
|
12816
|
+
if (groupByRaw !== "provider") throw validationError('"groupBy" must be "provider"');
|
|
12817
|
+
groupBy = "provider";
|
|
12818
|
+
}
|
|
12819
|
+
const hasSince = sinceRaw !== void 0 && sinceRaw !== "";
|
|
12820
|
+
const hasUntil = untilRaw !== void 0 && untilRaw !== "";
|
|
12821
|
+
const hasLastRuns = lastRunsRaw !== void 0 && lastRunsRaw !== "";
|
|
12822
|
+
if (hasLastRuns && (hasSince || hasUntil)) {
|
|
12823
|
+
throw validationError('"lastRuns" cannot be combined with "since"/"until" \u2014 use one or the other');
|
|
12824
|
+
}
|
|
12825
|
+
let sinceMs = null;
|
|
12826
|
+
let untilMs = null;
|
|
12827
|
+
if (hasSince) {
|
|
12828
|
+
const ms = Date.parse(sinceRaw);
|
|
12829
|
+
if (Number.isNaN(ms)) throw validationError('"since" must be an ISO 8601 date/time');
|
|
12830
|
+
sinceMs = ms;
|
|
12831
|
+
}
|
|
12832
|
+
if (hasUntil) {
|
|
12833
|
+
const ms = parseInclusiveEndMs(untilRaw);
|
|
12834
|
+
if (ms === null) throw validationError('"until" must be an ISO 8601 date/time');
|
|
12835
|
+
untilMs = ms;
|
|
12836
|
+
}
|
|
12837
|
+
if (sinceMs !== null && untilMs !== null && untilMs < sinceMs) {
|
|
12838
|
+
throw validationError('"until" must be on or after "since"');
|
|
12839
|
+
}
|
|
12840
|
+
let lastRuns = null;
|
|
12841
|
+
if (hasLastRuns) {
|
|
12842
|
+
const n = Number(lastRunsRaw);
|
|
12843
|
+
if (!Number.isInteger(n) || n <= 0) throw validationError('"lastRuns" must be a positive integer');
|
|
12844
|
+
lastRuns = n;
|
|
12845
|
+
}
|
|
12846
|
+
const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq16(queries.projectId, project.id)).all();
|
|
12847
|
+
let projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(and11(eq16(runs.projectId, project.id), eq16(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc8(runs.createdAt)).all().filter((r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial);
|
|
12848
|
+
if (sinceMs !== null) projectRuns = projectRuns.filter((r) => Date.parse(r.createdAt) >= sinceMs);
|
|
12849
|
+
if (untilMs !== null) projectRuns = projectRuns.filter((r) => Date.parse(r.createdAt) <= untilMs);
|
|
12850
|
+
if (lastRuns !== null) projectRuns = projectRuns.slice(0, lastRuns);
|
|
12851
|
+
const runCount = projectRuns.length;
|
|
12852
|
+
const runIds = projectRuns.map((r) => r.id);
|
|
12853
|
+
const snapshots = runIds.length > 0 && projectQueries.length > 0 ? app.db.select({
|
|
12854
|
+
queryId: querySnapshots.queryId,
|
|
12855
|
+
queryText: querySnapshots.queryText,
|
|
12856
|
+
provider: querySnapshots.provider,
|
|
12857
|
+
citationState: querySnapshots.citationState,
|
|
12858
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
12859
|
+
createdAt: querySnapshots.createdAt
|
|
12860
|
+
}).from(querySnapshots).where(inArray8(querySnapshots.runId, runIds)).all() : [];
|
|
12861
|
+
const stats = computeVisibilityStats({ queries: projectQueries, snapshots, groupBy });
|
|
12862
|
+
const response = {
|
|
12863
|
+
project: project.name,
|
|
12864
|
+
window: {
|
|
12865
|
+
since: hasSince ? sinceRaw : null,
|
|
12866
|
+
until: hasUntil ? untilRaw : null,
|
|
12867
|
+
lastRuns,
|
|
12868
|
+
runCount
|
|
12869
|
+
},
|
|
12870
|
+
totals: stats.totals,
|
|
12871
|
+
queries: stats.queries,
|
|
12872
|
+
// `groupBy` + `byProvider` appear together only when a breakdown was
|
|
12873
|
+
// requested; both are OMITTED otherwise (absent = no breakdown) so the
|
|
12874
|
+
// SDK types `groupBy` as `groupBy?: 'provider'` rather than a misleading
|
|
12875
|
+
// always-present literal.
|
|
12876
|
+
...groupBy === "provider" ? { groupBy, byProvider: stats.byProvider ?? [] } : {}
|
|
12877
|
+
};
|
|
12878
|
+
return reply.send(response);
|
|
12879
|
+
});
|
|
12880
|
+
}
|
|
12881
|
+
|
|
12723
12882
|
// ../api-routes/src/composites.ts
|
|
12724
|
-
import { eq as
|
|
12883
|
+
import { eq as eq17, and as and12, desc as desc9, sql as sql7, like, or as or4, inArray as inArray9 } from "drizzle-orm";
|
|
12725
12884
|
var TOP_INSIGHT_LIMIT = 5;
|
|
12726
12885
|
var SEARCH_HIT_HARD_LIMIT = 50;
|
|
12727
12886
|
var SEARCH_SNIPPET_RADIUS = 80;
|
|
@@ -12739,7 +12898,7 @@ async function compositeRoutes(app) {
|
|
|
12739
12898
|
const project = resolveProject(app.db, request.params.name);
|
|
12740
12899
|
const filterLocation = (request.query.location ?? "").trim() || null;
|
|
12741
12900
|
const sinceIso = parseSinceFilter(request.query.since);
|
|
12742
|
-
const allRunsRaw = app.db.select().from(runs).where(
|
|
12901
|
+
const allRunsRaw = app.db.select().from(runs).where(and12(eq17(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
|
|
12743
12902
|
const allRuns = allRunsRaw.filter((r) => runMatchesFilters(r, filterLocation, sinceIso));
|
|
12744
12903
|
const totalRuns = allRuns.length;
|
|
12745
12904
|
const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
|
|
@@ -12752,9 +12911,9 @@ async function compositeRoutes(app) {
|
|
|
12752
12911
|
const previousVisibilityRun = pickGroupRepresentative(previousVisRunGroup);
|
|
12753
12912
|
const latestRunRow = allRuns[0] ?? null;
|
|
12754
12913
|
const latestRun = latestRunRow ? { totalRuns, run: summarizeRun(latestRunRow) } : { totalRuns: 0, run: null };
|
|
12755
|
-
const healthRow = app.db.select().from(healthSnapshots).where(
|
|
12914
|
+
const healthRow = app.db.select().from(healthSnapshots).where(eq17(healthSnapshots.projectId, project.id)).orderBy(desc9(healthSnapshots.createdAt)).limit(1).get();
|
|
12756
12915
|
const health = healthRow ? mapHealthRow2(healthRow) : null;
|
|
12757
|
-
const insightRows = app.db.select().from(insights).where(
|
|
12916
|
+
const insightRows = app.db.select().from(insights).where(eq17(insights.projectId, project.id)).orderBy(desc9(insights.createdAt)).all();
|
|
12758
12917
|
const topInsights = insightRows.filter((row) => !row.dismissed).slice(0, TOP_INSIGHT_LIMIT).map(mapInsightRow2);
|
|
12759
12918
|
const sparklineRunIds = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => r.id);
|
|
12760
12919
|
const snapshotRunIds = new Set(sparklineRunIds);
|
|
@@ -12769,8 +12928,8 @@ async function compositeRoutes(app) {
|
|
|
12769
12928
|
previousSnapshots,
|
|
12770
12929
|
previousVisibilityRun?.createdAt ?? null
|
|
12771
12930
|
);
|
|
12772
|
-
const competitorRows = app.db.select().from(competitors).where(
|
|
12773
|
-
const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(
|
|
12931
|
+
const competitorRows = app.db.select().from(competitors).where(eq17(competitors.projectId, project.id)).all();
|
|
12932
|
+
const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq17(queries.projectId, project.id)).all();
|
|
12774
12933
|
const queryLookup = { byId: new Map(projectQueries.map((q) => [q.id, q.query])) };
|
|
12775
12934
|
const configuredApiProviders = project.providers.filter((p) => !p.startsWith("cdp:"));
|
|
12776
12935
|
const mentionShareCompetitors = competitorRows.map((c) => ({
|
|
@@ -12869,9 +13028,9 @@ async function compositeRoutes(app) {
|
|
|
12869
13028
|
citedDomains: querySnapshots.citedDomains,
|
|
12870
13029
|
rawResponse: querySnapshots.rawResponse,
|
|
12871
13030
|
createdAt: querySnapshots.createdAt
|
|
12872
|
-
}).from(querySnapshots).innerJoin(queries,
|
|
12873
|
-
|
|
12874
|
-
|
|
13031
|
+
}).from(querySnapshots).innerJoin(queries, eq17(querySnapshots.queryId, queries.id)).where(
|
|
13032
|
+
and12(
|
|
13033
|
+
eq17(queries.projectId, project.id),
|
|
12875
13034
|
or4(
|
|
12876
13035
|
sql7`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
|
|
12877
13036
|
sql7`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
|
|
@@ -12879,10 +13038,10 @@ async function compositeRoutes(app) {
|
|
|
12879
13038
|
like(queries.query, pattern)
|
|
12880
13039
|
)
|
|
12881
13040
|
)
|
|
12882
|
-
).orderBy(
|
|
13041
|
+
).orderBy(desc9(querySnapshots.createdAt)).limit(limit + 1).all());
|
|
12883
13042
|
const insightMatches = app.db.select().from(insights).where(
|
|
12884
|
-
|
|
12885
|
-
|
|
13043
|
+
and12(
|
|
13044
|
+
eq17(insights.projectId, project.id),
|
|
12886
13045
|
or4(
|
|
12887
13046
|
like(insights.title, pattern),
|
|
12888
13047
|
like(insights.query, pattern),
|
|
@@ -12890,7 +13049,7 @@ async function compositeRoutes(app) {
|
|
|
12890
13049
|
sql7`${insights.cause} LIKE ${pattern} ESCAPE '\\'`
|
|
12891
13050
|
)
|
|
12892
13051
|
)
|
|
12893
|
-
).orderBy(
|
|
13052
|
+
).orderBy(desc9(insights.createdAt)).limit(limit + 1).all();
|
|
12894
13053
|
const hits = [];
|
|
12895
13054
|
for (const row of snapshotMatches) {
|
|
12896
13055
|
hits.push(buildSnapshotHit(row, rawQuery));
|
|
@@ -12960,7 +13119,7 @@ function loadSnapshotsByRunIds(app, runIds) {
|
|
|
12960
13119
|
answerText: querySnapshots.answerText,
|
|
12961
13120
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
12962
13121
|
citedDomains: querySnapshots.citedDomains
|
|
12963
|
-
}).from(querySnapshots).where(
|
|
13122
|
+
}).from(querySnapshots).where(inArray9(querySnapshots.runId, [...runIds])).all());
|
|
12964
13123
|
for (const row of rows) {
|
|
12965
13124
|
const list = result.get(row.runId) ?? [];
|
|
12966
13125
|
list.push({
|
|
@@ -13078,8 +13237,8 @@ function buildSuggestedQueriesFromGsc(app, projectId, trackedQueries) {
|
|
|
13078
13237
|
// NULLIF guards the degenerate impressions=0 case (SQLite returns NULL,
|
|
13079
13238
|
// which the JS coerces to 0 — caught by the impression floor anyway).
|
|
13080
13239
|
avgPosition: sql7`COALESCE(SUM(${gscSearchData.position} * ${gscSearchData.impressions}) * 1.0 / NULLIF(SUM(${gscSearchData.impressions}), 0), 0)`
|
|
13081
|
-
}).from(gscSearchData).where(
|
|
13082
|
-
|
|
13240
|
+
}).from(gscSearchData).where(and12(
|
|
13241
|
+
eq17(gscSearchData.projectId, projectId),
|
|
13083
13242
|
sql7`${gscSearchData.date} >= ${cutoff}`,
|
|
13084
13243
|
sql7`${gscSearchData.impressions} > 0`
|
|
13085
13244
|
)).groupBy(gscSearchData.query).orderBy(sql7`SUM(${gscSearchData.impressions}) DESC`).limit(100).all();
|
|
@@ -13102,8 +13261,8 @@ function buildIndexCoverageScore(app, projectId) {
|
|
|
13102
13261
|
tooltip,
|
|
13103
13262
|
trend: []
|
|
13104
13263
|
};
|
|
13105
|
-
const gscRow = app.db.select().from(gscCoverageSnapshots).where(
|
|
13106
|
-
const bingRow = app.db.select().from(bingCoverageSnapshots).where(
|
|
13264
|
+
const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq17(gscCoverageSnapshots.projectId, projectId)).orderBy(desc9(gscCoverageSnapshots.date)).limit(1).get();
|
|
13265
|
+
const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq17(bingCoverageSnapshots.projectId, projectId)).orderBy(desc9(bingCoverageSnapshots.date)).limit(1).get();
|
|
13107
13266
|
const chosen = pickIndexCoverageRow(gscRow, bingRow);
|
|
13108
13267
|
if (!chosen) return empty;
|
|
13109
13268
|
const total = chosen.indexed + chosen.notIndexed;
|
|
@@ -13129,7 +13288,7 @@ function countGoogleDeindexedUrls(app, projectId) {
|
|
|
13129
13288
|
url: gscUrlInspections.url,
|
|
13130
13289
|
indexingState: gscUrlInspections.indexingState,
|
|
13131
13290
|
inspectedAt: gscUrlInspections.inspectedAt
|
|
13132
|
-
}).from(gscUrlInspections).where(
|
|
13291
|
+
}).from(gscUrlInspections).where(eq17(gscUrlInspections.projectId, projectId)).orderBy(desc9(gscUrlInspections.inspectedAt)).all();
|
|
13133
13292
|
if (rows.length === 0) return 0;
|
|
13134
13293
|
const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
|
|
13135
13294
|
const historyByUrl = /* @__PURE__ */ new Map();
|
|
@@ -13475,6 +13634,7 @@ var SCHEMA_TABLE = {
|
|
|
13475
13634
|
TrafficSourceListResponse: trafficSourceListResponseSchema,
|
|
13476
13635
|
TrafficStatusResponse: trafficStatusResponseSchema,
|
|
13477
13636
|
TrafficSyncResponse: trafficSyncResponseSchema,
|
|
13637
|
+
VisibilityStatsDto: visibilityStatsDtoSchema,
|
|
13478
13638
|
WordpressAuditPageDto: wordpressAuditPageDtoSchema,
|
|
13479
13639
|
WordpressBulkMetaResultDto: wordpressBulkMetaResultDtoSchema,
|
|
13480
13640
|
WordpressDiffDto: wordpressDiffDtoSchema,
|
|
@@ -13700,6 +13860,30 @@ var analyticsWindowParameter = {
|
|
|
13700
13860
|
description: "Time window for analytics queries.",
|
|
13701
13861
|
schema: { type: "string", enum: ["7d", "30d", "90d", "all"] }
|
|
13702
13862
|
};
|
|
13863
|
+
var sinceQueryParameter = {
|
|
13864
|
+
name: "since",
|
|
13865
|
+
in: "query",
|
|
13866
|
+
description: 'Inclusive lower bound on run createdAt (ISO 8601). A date-only value (YYYY-MM-DD) is the start of that UTC day. Mutually exclusive with "lastRuns".',
|
|
13867
|
+
schema: stringSchema
|
|
13868
|
+
};
|
|
13869
|
+
var untilQueryParameter = {
|
|
13870
|
+
name: "until",
|
|
13871
|
+
in: "query",
|
|
13872
|
+
description: 'Inclusive upper bound on run createdAt (ISO 8601). A date-only value (YYYY-MM-DD) covers the whole UTC day (through 23:59:59.999). Mutually exclusive with "lastRuns".',
|
|
13873
|
+
schema: stringSchema
|
|
13874
|
+
};
|
|
13875
|
+
var lastRunsQueryParameter = {
|
|
13876
|
+
name: "lastRuns",
|
|
13877
|
+
in: "query",
|
|
13878
|
+
description: 'Aggregate only the most recent N answer-visibility runs. Mutually exclusive with "since"/"until".',
|
|
13879
|
+
schema: integerSchema
|
|
13880
|
+
};
|
|
13881
|
+
var groupByProviderQueryParameter = {
|
|
13882
|
+
name: "groupBy",
|
|
13883
|
+
in: "query",
|
|
13884
|
+
description: 'Set to "provider" to include a per-provider breakdown whose counts sum to the pooled counts.',
|
|
13885
|
+
schema: { type: "string", enum: ["provider"] }
|
|
13886
|
+
};
|
|
13703
13887
|
var wordpressEnvQueryParameter = {
|
|
13704
13888
|
name: "env",
|
|
13705
13889
|
in: "query",
|
|
@@ -14426,6 +14610,19 @@ var routeCatalog = [
|
|
|
14426
14610
|
404: errorResponse("Project not found.")
|
|
14427
14611
|
}
|
|
14428
14612
|
},
|
|
14613
|
+
{
|
|
14614
|
+
method: "get",
|
|
14615
|
+
path: "/api/v1/projects/{name}/visibility-stats",
|
|
14616
|
+
summary: "Get aggregated mention/citation stats per query",
|
|
14617
|
+
description: "Per-query mention (answer-text) and citation (source-list) counts with a sample size, pooled across many answer-visibility runs (probe-excluded). Tri-state aware: `checked` counts only snapshots where answerMentioned was recorded (null = not checked is excluded). Lets a consumer compute confidence-aware (e.g. Wilson) proportions without N+1 run fetches. With no since/until/lastRuns, EVERY completed/partial answer-visibility run is pooled \u2014 `window.runCount` reports how many; bound the window with lastRuns or since/until for a recent sample. Set groupBy=provider for a per-provider breakdown whose counts sum to the pooled counts.",
|
|
14618
|
+
tags: ["analytics"],
|
|
14619
|
+
parameters: [nameParameter, sinceQueryParameter, untilQueryParameter, lastRunsQueryParameter, groupByProviderQueryParameter],
|
|
14620
|
+
responses: {
|
|
14621
|
+
200: jsonResponse("Aggregated visibility stats returned.", "VisibilityStatsDto"),
|
|
14622
|
+
400: errorResponse("Invalid query parameters."),
|
|
14623
|
+
404: errorResponse("Project not found.")
|
|
14624
|
+
}
|
|
14625
|
+
},
|
|
14429
14626
|
{
|
|
14430
14627
|
method: "get",
|
|
14431
14628
|
path: "/api/v1/projects/{name}/snapshots/diff",
|
|
@@ -17691,7 +17888,7 @@ async function settingsRoutes(app, opts) {
|
|
|
17691
17888
|
|
|
17692
17889
|
// ../api-routes/src/keys.ts
|
|
17693
17890
|
import crypto12 from "crypto";
|
|
17694
|
-
import { desc as
|
|
17891
|
+
import { desc as desc10, eq as eq18 } from "drizzle-orm";
|
|
17695
17892
|
var KEYS_WRITE_SCOPE = "keys.write";
|
|
17696
17893
|
function toApiKeyDto(row) {
|
|
17697
17894
|
const scopes = Array.isArray(row.scopes) ? row.scopes : [];
|
|
@@ -17708,7 +17905,7 @@ function toApiKeyDto(row) {
|
|
|
17708
17905
|
}
|
|
17709
17906
|
async function keysRoutes(app) {
|
|
17710
17907
|
app.get("/keys", async () => {
|
|
17711
|
-
const rows = app.db.select().from(apiKeys).orderBy(
|
|
17908
|
+
const rows = app.db.select().from(apiKeys).orderBy(desc10(apiKeys.createdAt)).all();
|
|
17712
17909
|
return { keys: rows.map(toApiKeyDto) };
|
|
17713
17910
|
});
|
|
17714
17911
|
app.get("/keys/self", async (request) => {
|
|
@@ -17716,7 +17913,7 @@ async function keysRoutes(app) {
|
|
|
17716
17913
|
if (!id) {
|
|
17717
17914
|
throw notFound("API key", "self");
|
|
17718
17915
|
}
|
|
17719
|
-
const row = app.db.select().from(apiKeys).where(
|
|
17916
|
+
const row = app.db.select().from(apiKeys).where(eq18(apiKeys.id, id)).get();
|
|
17720
17917
|
if (!row) {
|
|
17721
17918
|
throw notFound("API key", id);
|
|
17722
17919
|
}
|
|
@@ -17768,7 +17965,7 @@ async function keysRoutes(app) {
|
|
|
17768
17965
|
app.post("/keys/:id/revoke", async (request) => {
|
|
17769
17966
|
requireScope(request, KEYS_WRITE_SCOPE);
|
|
17770
17967
|
const { id } = request.params;
|
|
17771
|
-
const row = app.db.select().from(apiKeys).where(
|
|
17968
|
+
const row = app.db.select().from(apiKeys).where(eq18(apiKeys.id, id)).get();
|
|
17772
17969
|
if (!row) {
|
|
17773
17970
|
throw notFound("API key", id);
|
|
17774
17971
|
}
|
|
@@ -17780,7 +17977,7 @@ async function keysRoutes(app) {
|
|
|
17780
17977
|
}
|
|
17781
17978
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17782
17979
|
app.db.transaction((tx) => {
|
|
17783
|
-
tx.update(apiKeys).set({ revokedAt: now }).where(
|
|
17980
|
+
tx.update(apiKeys).set({ revokedAt: now }).where(eq18(apiKeys.id, id)).run();
|
|
17784
17981
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
17785
17982
|
actor: "api",
|
|
17786
17983
|
action: "api-key.revoked",
|
|
@@ -17853,7 +18050,7 @@ async function telemetryRoutes(app, opts) {
|
|
|
17853
18050
|
|
|
17854
18051
|
// ../api-routes/src/schedules.ts
|
|
17855
18052
|
import crypto13 from "crypto";
|
|
17856
|
-
import { and as
|
|
18053
|
+
import { and as and13, eq as eq19 } from "drizzle-orm";
|
|
17857
18054
|
function parseKindParam(raw) {
|
|
17858
18055
|
if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
|
|
17859
18056
|
const parsed = schedulableRunKindSchema.safeParse(raw);
|
|
@@ -17880,7 +18077,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
17880
18077
|
if (!sourceId) {
|
|
17881
18078
|
throw validationError('"sourceId" is required when kind is "traffic-sync"');
|
|
17882
18079
|
}
|
|
17883
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
18080
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq19(trafficSources.id, sourceId)).get();
|
|
17884
18081
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
17885
18082
|
throw notFound("Traffic source", sourceId);
|
|
17886
18083
|
}
|
|
@@ -17925,7 +18122,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
17925
18122
|
}
|
|
17926
18123
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17927
18124
|
const enabledBool = enabled !== false;
|
|
17928
|
-
const existing = app.db.select().from(schedules).where(
|
|
18125
|
+
const existing = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
|
|
17929
18126
|
if (existing) {
|
|
17930
18127
|
app.db.update(schedules).set({
|
|
17931
18128
|
cronExpr,
|
|
@@ -17935,7 +18132,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
17935
18132
|
sourceId: sourceId ?? null,
|
|
17936
18133
|
enabled: enabledBool,
|
|
17937
18134
|
updatedAt: now
|
|
17938
|
-
}).where(
|
|
18135
|
+
}).where(eq19(schedules.id, existing.id)).run();
|
|
17939
18136
|
} else {
|
|
17940
18137
|
app.db.insert(schedules).values({
|
|
17941
18138
|
id: crypto13.randomUUID(),
|
|
@@ -17959,13 +18156,13 @@ async function scheduleRoutes(app, opts) {
|
|
|
17959
18156
|
diff: { kind, cronExpr, preset, timezone, providers, sourceId }
|
|
17960
18157
|
});
|
|
17961
18158
|
opts.onScheduleUpdated?.("upsert", project.id, kind);
|
|
17962
|
-
const schedule = app.db.select().from(schedules).where(
|
|
18159
|
+
const schedule = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
|
|
17963
18160
|
return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
|
|
17964
18161
|
});
|
|
17965
18162
|
app.get("/projects/:name/schedule", async (request, reply) => {
|
|
17966
18163
|
const project = resolveProject(app.db, request.params.name);
|
|
17967
18164
|
const kind = parseKindParam(request.query?.kind);
|
|
17968
|
-
const schedule = app.db.select().from(schedules).where(
|
|
18165
|
+
const schedule = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
|
|
17969
18166
|
if (!schedule) {
|
|
17970
18167
|
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
17971
18168
|
}
|
|
@@ -17974,11 +18171,11 @@ async function scheduleRoutes(app, opts) {
|
|
|
17974
18171
|
app.delete("/projects/:name/schedule", async (request, reply) => {
|
|
17975
18172
|
const project = resolveProject(app.db, request.params.name);
|
|
17976
18173
|
const kind = parseKindParam(request.query?.kind);
|
|
17977
|
-
const schedule = app.db.select().from(schedules).where(
|
|
18174
|
+
const schedule = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
|
|
17978
18175
|
if (!schedule) {
|
|
17979
18176
|
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
17980
18177
|
}
|
|
17981
|
-
app.db.delete(schedules).where(
|
|
18178
|
+
app.db.delete(schedules).where(eq19(schedules.id, schedule.id)).run();
|
|
17982
18179
|
writeAuditLog(app.db, {
|
|
17983
18180
|
projectId: project.id,
|
|
17984
18181
|
actor: "api",
|
|
@@ -18011,7 +18208,7 @@ function formatSchedule(row) {
|
|
|
18011
18208
|
|
|
18012
18209
|
// ../api-routes/src/notifications.ts
|
|
18013
18210
|
import crypto14 from "crypto";
|
|
18014
|
-
import { eq as
|
|
18211
|
+
import { eq as eq20 } from "drizzle-orm";
|
|
18015
18212
|
var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed", "insight.critical", "insight.high"];
|
|
18016
18213
|
async function notificationRoutes(app, opts = {}) {
|
|
18017
18214
|
const allowLoopback = opts.allowLoopbackWebhooks === true;
|
|
@@ -18051,22 +18248,22 @@ async function notificationRoutes(app, opts = {}) {
|
|
|
18051
18248
|
diff: { channel, ...redactNotificationUrl(url), events }
|
|
18052
18249
|
});
|
|
18053
18250
|
return reply.status(201).send({
|
|
18054
|
-
...formatNotification(app.db.select().from(notifications).where(
|
|
18251
|
+
...formatNotification(app.db.select().from(notifications).where(eq20(notifications.id, id)).get()),
|
|
18055
18252
|
webhookSecret
|
|
18056
18253
|
});
|
|
18057
18254
|
});
|
|
18058
18255
|
app.get("/projects/:name/notifications", async (request, reply) => {
|
|
18059
18256
|
const project = resolveProject(app.db, request.params.name);
|
|
18060
|
-
const rows = app.db.select().from(notifications).where(
|
|
18257
|
+
const rows = app.db.select().from(notifications).where(eq20(notifications.projectId, project.id)).all();
|
|
18061
18258
|
return reply.send(rows.map(formatNotification));
|
|
18062
18259
|
});
|
|
18063
18260
|
app.delete("/projects/:name/notifications/:id", async (request, reply) => {
|
|
18064
18261
|
const project = resolveProject(app.db, request.params.name);
|
|
18065
|
-
const notification = app.db.select().from(notifications).where(
|
|
18262
|
+
const notification = app.db.select().from(notifications).where(eq20(notifications.id, request.params.id)).get();
|
|
18066
18263
|
if (!notification || notification.projectId !== project.id) {
|
|
18067
18264
|
throw notFound("Notification", request.params.id);
|
|
18068
18265
|
}
|
|
18069
|
-
app.db.delete(notifications).where(
|
|
18266
|
+
app.db.delete(notifications).where(eq20(notifications.id, notification.id)).run();
|
|
18070
18267
|
writeAuditLog(app.db, {
|
|
18071
18268
|
projectId: project.id,
|
|
18072
18269
|
actor: "api",
|
|
@@ -18078,7 +18275,7 @@ async function notificationRoutes(app, opts = {}) {
|
|
|
18078
18275
|
});
|
|
18079
18276
|
app.post("/projects/:name/notifications/:id/test", async (request, reply) => {
|
|
18080
18277
|
const project = resolveProject(app.db, request.params.name);
|
|
18081
|
-
const notification = app.db.select().from(notifications).where(
|
|
18278
|
+
const notification = app.db.select().from(notifications).where(eq20(notifications.id, request.params.id)).get();
|
|
18082
18279
|
if (!notification || notification.projectId !== project.id) {
|
|
18083
18280
|
throw notFound("Notification", request.params.id);
|
|
18084
18281
|
}
|
|
@@ -18131,7 +18328,7 @@ function formatNotification(row) {
|
|
|
18131
18328
|
|
|
18132
18329
|
// ../api-routes/src/google.ts
|
|
18133
18330
|
import crypto17 from "crypto";
|
|
18134
|
-
import { eq as
|
|
18331
|
+
import { eq as eq21, and as and14, desc as desc11, sql as sql8, inArray as inArray10 } from "drizzle-orm";
|
|
18135
18332
|
|
|
18136
18333
|
// ../api-routes/src/gbp-summary.ts
|
|
18137
18334
|
function computeMetricTotals(rows) {
|
|
@@ -19857,7 +20054,7 @@ async function googleRoutes(app, opts) {
|
|
|
19857
20054
|
if (!projectId) {
|
|
19858
20055
|
return reply.status(400).send("Stale OAuth state \u2014 restart the connect flow.");
|
|
19859
20056
|
}
|
|
19860
|
-
const project = app.db.select().from(projects).where(
|
|
20057
|
+
const project = app.db.select().from(projects).where(eq21(projects.id, projectId)).get();
|
|
19861
20058
|
if (!project) {
|
|
19862
20059
|
return reply.status(400).send("Project no longer exists. Restart the connect flow.");
|
|
19863
20060
|
}
|
|
@@ -19989,14 +20186,14 @@ async function googleRoutes(app, opts) {
|
|
|
19989
20186
|
if (opts.onGscSyncRequested) {
|
|
19990
20187
|
opts.onGscSyncRequested(runId, project.id, { days, full });
|
|
19991
20188
|
}
|
|
19992
|
-
const run = app.db.select().from(runs).where(
|
|
20189
|
+
const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
|
|
19993
20190
|
return run;
|
|
19994
20191
|
});
|
|
19995
20192
|
app.get("/projects/:name/google/gsc/performance", async (request) => {
|
|
19996
20193
|
const project = resolveProject(app.db, request.params.name);
|
|
19997
20194
|
const { startDate, endDate, query, page, limit, offset } = request.query;
|
|
19998
20195
|
const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
|
|
19999
|
-
const conditions = [
|
|
20196
|
+
const conditions = [eq21(gscSearchData.projectId, project.id)];
|
|
20000
20197
|
if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
|
|
20001
20198
|
else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
|
|
20002
20199
|
if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
|
|
@@ -20004,7 +20201,7 @@ async function googleRoutes(app, opts) {
|
|
|
20004
20201
|
if (page) conditions.push(sql8`${gscSearchData.page} LIKE ${"%" + escapeLikePattern(page) + "%"} ESCAPE '\\'`);
|
|
20005
20202
|
const limitVal = Math.max(parseInt(limit ?? "500", 10) || 0, 1);
|
|
20006
20203
|
const offsetVal = Math.max(parseInt(offset ?? "0", 10) || 0, 0);
|
|
20007
|
-
const rows = app.db.select().from(gscSearchData).where(
|
|
20204
|
+
const rows = app.db.select().from(gscSearchData).where(and14(...conditions)).orderBy(desc11(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
|
|
20008
20205
|
return rows.map((r) => ({
|
|
20009
20206
|
date: r.date,
|
|
20010
20207
|
query: r.query,
|
|
@@ -20021,7 +20218,7 @@ async function googleRoutes(app, opts) {
|
|
|
20021
20218
|
const project = resolveProject(app.db, request.params.name);
|
|
20022
20219
|
const { startDate, endDate } = request.query;
|
|
20023
20220
|
const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
|
|
20024
|
-
const conditions = [
|
|
20221
|
+
const conditions = [eq21(gscSearchData.projectId, project.id)];
|
|
20025
20222
|
if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
|
|
20026
20223
|
else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
|
|
20027
20224
|
if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
|
|
@@ -20029,7 +20226,7 @@ async function googleRoutes(app, opts) {
|
|
|
20029
20226
|
date: gscSearchData.date,
|
|
20030
20227
|
clicks: sql8`COALESCE(SUM(${gscSearchData.clicks}), 0)`,
|
|
20031
20228
|
impressions: sql8`COALESCE(SUM(${gscSearchData.impressions}), 0)`
|
|
20032
|
-
}).from(gscSearchData).where(
|
|
20229
|
+
}).from(gscSearchData).where(and14(...conditions)).groupBy(gscSearchData.date).orderBy(gscSearchData.date).all();
|
|
20033
20230
|
const daily = rows.map((r) => ({
|
|
20034
20231
|
date: r.date,
|
|
20035
20232
|
clicks: r.clicks,
|
|
@@ -20112,9 +20309,9 @@ async function googleRoutes(app, opts) {
|
|
|
20112
20309
|
app.get("/projects/:name/google/gsc/inspections", async (request) => {
|
|
20113
20310
|
const project = resolveProject(app.db, request.params.name);
|
|
20114
20311
|
const { url, limit } = request.query;
|
|
20115
|
-
const conditions = [
|
|
20116
|
-
if (url) conditions.push(
|
|
20117
|
-
const rows = app.db.select().from(gscUrlInspections).where(
|
|
20312
|
+
const conditions = [eq21(gscUrlInspections.projectId, project.id)];
|
|
20313
|
+
if (url) conditions.push(eq21(gscUrlInspections.url, url));
|
|
20314
|
+
const rows = app.db.select().from(gscUrlInspections).where(and14(...conditions)).orderBy(desc11(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
|
|
20118
20315
|
return rows.map((r) => ({
|
|
20119
20316
|
id: r.id,
|
|
20120
20317
|
url: r.url,
|
|
@@ -20133,7 +20330,7 @@ async function googleRoutes(app, opts) {
|
|
|
20133
20330
|
});
|
|
20134
20331
|
app.get("/projects/:name/google/gsc/deindexed", async (request) => {
|
|
20135
20332
|
const project = resolveProject(app.db, request.params.name);
|
|
20136
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
20333
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
|
|
20137
20334
|
const byUrl = /* @__PURE__ */ new Map();
|
|
20138
20335
|
for (const row of allInspections) {
|
|
20139
20336
|
const existing = byUrl.get(row.url);
|
|
@@ -20161,7 +20358,7 @@ async function googleRoutes(app, opts) {
|
|
|
20161
20358
|
});
|
|
20162
20359
|
app.get("/projects/:name/google/gsc/coverage", async (request) => {
|
|
20163
20360
|
const project = resolveProject(app.db, request.params.name);
|
|
20164
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
20361
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
|
|
20165
20362
|
const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
|
|
20166
20363
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
20167
20364
|
const historyByUrl = /* @__PURE__ */ new Map();
|
|
@@ -20210,7 +20407,7 @@ async function googleRoutes(app, opts) {
|
|
|
20210
20407
|
const total = latestByUrl.size;
|
|
20211
20408
|
const indexed = indexedUrls.length;
|
|
20212
20409
|
const notIndexed = notIndexedUrls.length;
|
|
20213
|
-
const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(
|
|
20410
|
+
const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq21(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.createdAt)).limit(1).get();
|
|
20214
20411
|
const lastSyncedAt = latestSnapshot?.createdAt ?? null;
|
|
20215
20412
|
const formatRow = (r) => ({
|
|
20216
20413
|
id: r.id,
|
|
@@ -20261,7 +20458,7 @@ async function googleRoutes(app, opts) {
|
|
|
20261
20458
|
const project = resolveProject(app.db, request.params.name);
|
|
20262
20459
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
20263
20460
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
20264
|
-
const rows = app.db.select().from(gscCoverageSnapshots).where(
|
|
20461
|
+
const rows = app.db.select().from(gscCoverageSnapshots).where(eq21(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.date)).limit(limit).all();
|
|
20265
20462
|
return rows.map((r) => ({
|
|
20266
20463
|
date: r.date,
|
|
20267
20464
|
indexed: r.indexed,
|
|
@@ -20330,7 +20527,7 @@ async function googleRoutes(app, opts) {
|
|
|
20330
20527
|
if (opts.onInspectSitemapRequested) {
|
|
20331
20528
|
opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl });
|
|
20332
20529
|
}
|
|
20333
|
-
const run = app.db.select().from(runs).where(
|
|
20530
|
+
const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
|
|
20334
20531
|
return { sitemaps, primarySitemapUrl: sitemapUrl, run };
|
|
20335
20532
|
});
|
|
20336
20533
|
app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
|
|
@@ -20357,7 +20554,7 @@ async function googleRoutes(app, opts) {
|
|
|
20357
20554
|
if (opts.onInspectSitemapRequested) {
|
|
20358
20555
|
opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
|
|
20359
20556
|
}
|
|
20360
|
-
const run = app.db.select().from(runs).where(
|
|
20557
|
+
const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
|
|
20361
20558
|
return run;
|
|
20362
20559
|
});
|
|
20363
20560
|
app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
|
|
@@ -20404,7 +20601,7 @@ async function googleRoutes(app, opts) {
|
|
|
20404
20601
|
const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
20405
20602
|
let urlsToNotify = request.body?.urls ?? [];
|
|
20406
20603
|
if (request.body?.allUnindexed) {
|
|
20407
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
20604
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
|
|
20408
20605
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
20409
20606
|
for (const row of allInspections) {
|
|
20410
20607
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -20515,7 +20712,7 @@ async function googleRoutes(app, opts) {
|
|
|
20515
20712
|
};
|
|
20516
20713
|
}
|
|
20517
20714
|
function listSelectionResponse(projectId) {
|
|
20518
|
-
const rows = app.db.select().from(gbpLocations).where(
|
|
20715
|
+
const rows = app.db.select().from(gbpLocations).where(eq21(gbpLocations.projectId, projectId)).all();
|
|
20519
20716
|
const dtos = rows.map(rowToDto2);
|
|
20520
20717
|
return {
|
|
20521
20718
|
locations: dtos,
|
|
@@ -20524,15 +20721,15 @@ async function googleRoutes(app, opts) {
|
|
|
20524
20721
|
};
|
|
20525
20722
|
}
|
|
20526
20723
|
function clearGbpProjectData(tx, projectId) {
|
|
20527
|
-
tx.delete(gbpDailyMetrics).where(
|
|
20528
|
-
tx.delete(gbpKeywordImpressions).where(
|
|
20529
|
-
tx.delete(gbpKeywordMonthly).where(
|
|
20530
|
-
tx.delete(gbpPlaceActions).where(
|
|
20531
|
-
tx.delete(gbpLodgingSnapshots).where(
|
|
20532
|
-
tx.delete(gbpLocations).where(
|
|
20724
|
+
tx.delete(gbpDailyMetrics).where(eq21(gbpDailyMetrics.projectId, projectId)).run();
|
|
20725
|
+
tx.delete(gbpKeywordImpressions).where(eq21(gbpKeywordImpressions.projectId, projectId)).run();
|
|
20726
|
+
tx.delete(gbpKeywordMonthly).where(eq21(gbpKeywordMonthly.projectId, projectId)).run();
|
|
20727
|
+
tx.delete(gbpPlaceActions).where(eq21(gbpPlaceActions.projectId, projectId)).run();
|
|
20728
|
+
tx.delete(gbpLodgingSnapshots).where(eq21(gbpLodgingSnapshots.projectId, projectId)).run();
|
|
20729
|
+
tx.delete(gbpLocations).where(eq21(gbpLocations.projectId, projectId)).run();
|
|
20533
20730
|
}
|
|
20534
20731
|
function currentProjectAccount(projectId) {
|
|
20535
|
-
const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(
|
|
20732
|
+
const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq21(gbpLocations.projectId, projectId)).limit(1).get();
|
|
20536
20733
|
return row?.accountName ?? null;
|
|
20537
20734
|
}
|
|
20538
20735
|
app.post("/projects/:name/gbp/locations/discover", async (request) => {
|
|
@@ -20602,7 +20799,7 @@ async function googleRoutes(app, opts) {
|
|
|
20602
20799
|
app.db.transaction((tx) => {
|
|
20603
20800
|
if (switching) clearGbpProjectData(tx, project.id);
|
|
20604
20801
|
for (const remote of remoteLocations) {
|
|
20605
|
-
const existing = tx.select().from(gbpLocations).where(
|
|
20802
|
+
const existing = tx.select().from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), eq21(gbpLocations.locationName, remote.name))).get();
|
|
20606
20803
|
if (existing) {
|
|
20607
20804
|
tx.update(gbpLocations).set({
|
|
20608
20805
|
accountName,
|
|
@@ -20613,7 +20810,7 @@ async function googleRoutes(app, opts) {
|
|
|
20613
20810
|
placeId: remote.metadata?.placeId ?? null,
|
|
20614
20811
|
mapsUri: remote.metadata?.mapsUri ?? null,
|
|
20615
20812
|
updatedAt: now
|
|
20616
|
-
}).where(
|
|
20813
|
+
}).where(eq21(gbpLocations.id, existing.id)).run();
|
|
20617
20814
|
} else {
|
|
20618
20815
|
tx.insert(gbpLocations).values({
|
|
20619
20816
|
id: crypto17.randomUUID(),
|
|
@@ -20696,11 +20893,11 @@ async function googleRoutes(app, opts) {
|
|
|
20696
20893
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid selection request");
|
|
20697
20894
|
}
|
|
20698
20895
|
const { selected } = parsed.data;
|
|
20699
|
-
const existing = app.db.select().from(gbpLocations).where(
|
|
20896
|
+
const existing = app.db.select().from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), eq21(gbpLocations.locationName, locationName))).get();
|
|
20700
20897
|
if (!existing) throw notFound("GBP location", locationName);
|
|
20701
20898
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20702
20899
|
app.db.transaction((tx) => {
|
|
20703
|
-
tx.update(gbpLocations).set({ selected, updatedAt: now }).where(
|
|
20900
|
+
tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq21(gbpLocations.id, existing.id)).run();
|
|
20704
20901
|
writeAuditLog(tx, {
|
|
20705
20902
|
projectId: project.id,
|
|
20706
20903
|
actor: "api",
|
|
@@ -20709,7 +20906,7 @@ async function googleRoutes(app, opts) {
|
|
|
20709
20906
|
entityId: locationName
|
|
20710
20907
|
});
|
|
20711
20908
|
});
|
|
20712
|
-
const refreshed = app.db.select().from(gbpLocations).where(
|
|
20909
|
+
const refreshed = app.db.select().from(gbpLocations).where(eq21(gbpLocations.id, existing.id)).get();
|
|
20713
20910
|
return rowToDto2(refreshed);
|
|
20714
20911
|
});
|
|
20715
20912
|
app.delete("/projects/:name/gbp/connection", async (request, reply) => {
|
|
@@ -20753,10 +20950,10 @@ async function googleRoutes(app, opts) {
|
|
|
20753
20950
|
});
|
|
20754
20951
|
app.get("/projects/:name/gbp/metrics", async (request) => {
|
|
20755
20952
|
const project = resolveProject(app.db, request.params.name);
|
|
20756
|
-
const conditions = [
|
|
20757
|
-
if (request.query.locationName) conditions.push(
|
|
20758
|
-
if (request.query.metric) conditions.push(
|
|
20759
|
-
const rows = app.db.select().from(gbpDailyMetrics).where(
|
|
20953
|
+
const conditions = [eq21(gbpDailyMetrics.projectId, project.id)];
|
|
20954
|
+
if (request.query.locationName) conditions.push(eq21(gbpDailyMetrics.locationName, request.query.locationName));
|
|
20955
|
+
if (request.query.metric) conditions.push(eq21(gbpDailyMetrics.metric, request.query.metric));
|
|
20956
|
+
const rows = app.db.select().from(gbpDailyMetrics).where(and14(...conditions)).orderBy(desc11(gbpDailyMetrics.date)).all();
|
|
20760
20957
|
return {
|
|
20761
20958
|
metrics: rows.map((r) => ({ locationName: r.locationName, date: r.date, metric: r.metric, value: r.value })),
|
|
20762
20959
|
total: rows.length
|
|
@@ -20764,9 +20961,9 @@ async function googleRoutes(app, opts) {
|
|
|
20764
20961
|
});
|
|
20765
20962
|
app.get("/projects/:name/gbp/keywords", async (request) => {
|
|
20766
20963
|
const project = resolveProject(app.db, request.params.name);
|
|
20767
|
-
const conditions = [
|
|
20768
|
-
if (request.query.locationName) conditions.push(
|
|
20769
|
-
const rows = app.db.select().from(gbpKeywordImpressions).where(
|
|
20964
|
+
const conditions = [eq21(gbpKeywordImpressions.projectId, project.id)];
|
|
20965
|
+
if (request.query.locationName) conditions.push(eq21(gbpKeywordImpressions.locationName, request.query.locationName));
|
|
20966
|
+
const rows = app.db.select().from(gbpKeywordImpressions).where(and14(...conditions)).all();
|
|
20770
20967
|
rows.sort((a, b) => (b.valueCount ?? -1) - (a.valueCount ?? -1));
|
|
20771
20968
|
const thresholded = rows.filter((r) => r.valueThreshold !== null).length;
|
|
20772
20969
|
return {
|
|
@@ -20784,9 +20981,9 @@ async function googleRoutes(app, opts) {
|
|
|
20784
20981
|
});
|
|
20785
20982
|
app.get("/projects/:name/gbp/place-actions", async (request) => {
|
|
20786
20983
|
const project = resolveProject(app.db, request.params.name);
|
|
20787
|
-
const conditions = [
|
|
20788
|
-
if (request.query.locationName) conditions.push(
|
|
20789
|
-
const rows = app.db.select().from(gbpPlaceActions).where(
|
|
20984
|
+
const conditions = [eq21(gbpPlaceActions.projectId, project.id)];
|
|
20985
|
+
if (request.query.locationName) conditions.push(eq21(gbpPlaceActions.locationName, request.query.locationName));
|
|
20986
|
+
const rows = app.db.select().from(gbpPlaceActions).where(and14(...conditions)).all();
|
|
20790
20987
|
return {
|
|
20791
20988
|
placeActions: rows.map((r) => ({
|
|
20792
20989
|
locationName: r.locationName,
|
|
@@ -20801,9 +20998,9 @@ async function googleRoutes(app, opts) {
|
|
|
20801
20998
|
});
|
|
20802
20999
|
app.get("/projects/:name/gbp/lodging", async (request) => {
|
|
20803
21000
|
const project = resolveProject(app.db, request.params.name);
|
|
20804
|
-
const conditions = [
|
|
20805
|
-
if (request.query.locationName) conditions.push(
|
|
20806
|
-
const rows = app.db.select().from(gbpLodgingSnapshots).where(
|
|
21001
|
+
const conditions = [eq21(gbpLodgingSnapshots.projectId, project.id)];
|
|
21002
|
+
if (request.query.locationName) conditions.push(eq21(gbpLodgingSnapshots.locationName, request.query.locationName));
|
|
21003
|
+
const rows = app.db.select().from(gbpLodgingSnapshots).where(and14(...conditions)).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
|
|
20807
21004
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
20808
21005
|
for (const row of rows) {
|
|
20809
21006
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -20818,9 +21015,9 @@ async function googleRoutes(app, opts) {
|
|
|
20818
21015
|
});
|
|
20819
21016
|
app.get("/projects/:name/gbp/places", async (request) => {
|
|
20820
21017
|
const project = resolveProject(app.db, request.params.name);
|
|
20821
|
-
const conditions = [
|
|
20822
|
-
if (request.query.locationName) conditions.push(
|
|
20823
|
-
const rows = app.db.select().from(gbpPlaceDetails).where(
|
|
21018
|
+
const conditions = [eq21(gbpPlaceDetails.projectId, project.id)];
|
|
21019
|
+
if (request.query.locationName) conditions.push(eq21(gbpPlaceDetails.locationName, request.query.locationName));
|
|
21020
|
+
const rows = app.db.select().from(gbpPlaceDetails).where(and14(...conditions)).orderBy(desc11(gbpPlaceDetails.syncedAt)).all();
|
|
20824
21021
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
20825
21022
|
for (const row of rows) {
|
|
20826
21023
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -20839,7 +21036,7 @@ async function googleRoutes(app, opts) {
|
|
|
20839
21036
|
app.get("/projects/:name/gbp/summary", async (request) => {
|
|
20840
21037
|
const project = resolveProject(app.db, request.params.name);
|
|
20841
21038
|
const locationName = request.query.locationName ?? null;
|
|
20842
|
-
const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(
|
|
21039
|
+
const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), eq21(gbpLocations.selected, true))).all().map((r) => r.n);
|
|
20843
21040
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
20844
21041
|
if (locationNames.length === 0) {
|
|
20845
21042
|
return buildGbpSummary({
|
|
@@ -20852,10 +21049,10 @@ async function googleRoutes(app, opts) {
|
|
|
20852
21049
|
lodging: []
|
|
20853
21050
|
});
|
|
20854
21051
|
}
|
|
20855
|
-
const metricRows = app.db.select().from(gbpDailyMetrics).where(
|
|
20856
|
-
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(
|
|
20857
|
-
const placeActionRows = app.db.select().from(gbpPlaceActions).where(
|
|
20858
|
-
const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(
|
|
21052
|
+
const metricRows = app.db.select().from(gbpDailyMetrics).where(and14(eq21(gbpDailyMetrics.projectId, project.id), inArray10(gbpDailyMetrics.locationName, locationNames))).all();
|
|
21053
|
+
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and14(eq21(gbpKeywordImpressions.projectId, project.id), inArray10(gbpKeywordImpressions.locationName, locationNames))).all();
|
|
21054
|
+
const placeActionRows = app.db.select().from(gbpPlaceActions).where(and14(eq21(gbpPlaceActions.projectId, project.id), inArray10(gbpPlaceActions.locationName, locationNames))).all();
|
|
21055
|
+
const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and14(eq21(gbpLodgingSnapshots.projectId, project.id), inArray10(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
|
|
20859
21056
|
const latestLodgingByLocation = /* @__PURE__ */ new Map();
|
|
20860
21057
|
for (const row of lodgingRows) {
|
|
20861
21058
|
if (!latestLodgingByLocation.has(row.locationName)) {
|
|
@@ -20876,7 +21073,7 @@ async function googleRoutes(app, opts) {
|
|
|
20876
21073
|
|
|
20877
21074
|
// ../api-routes/src/ads.ts
|
|
20878
21075
|
import crypto18 from "crypto";
|
|
20879
|
-
import { eq as
|
|
21076
|
+
import { eq as eq22, and as and15, asc as asc2, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
|
|
20880
21077
|
function statusDto(row) {
|
|
20881
21078
|
if (!row) return { connected: false };
|
|
20882
21079
|
return {
|
|
@@ -20925,7 +21122,7 @@ async function adsRoutes(app, opts) {
|
|
|
20925
21122
|
createdAt: existingCfg?.createdAt ?? now,
|
|
20926
21123
|
updatedAt: now
|
|
20927
21124
|
});
|
|
20928
|
-
const existingRow = app.db.select().from(adsConnections).where(
|
|
21125
|
+
const existingRow = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
|
|
20929
21126
|
app.db.transaction((tx) => {
|
|
20930
21127
|
if (existingRow) {
|
|
20931
21128
|
tx.update(adsConnections).set({
|
|
@@ -20935,7 +21132,7 @@ async function adsRoutes(app, opts) {
|
|
|
20935
21132
|
timezone: account.timezone,
|
|
20936
21133
|
status: account.status,
|
|
20937
21134
|
updatedAt: now
|
|
20938
|
-
}).where(
|
|
21135
|
+
}).where(eq22(adsConnections.id, existingRow.id)).run();
|
|
20939
21136
|
} else {
|
|
20940
21137
|
tx.insert(adsConnections).values({
|
|
20941
21138
|
id: crypto18.randomUUID(),
|
|
@@ -20957,16 +21154,16 @@ async function adsRoutes(app, opts) {
|
|
|
20957
21154
|
entityId: account.id
|
|
20958
21155
|
}));
|
|
20959
21156
|
});
|
|
20960
|
-
const row = app.db.select().from(adsConnections).where(
|
|
21157
|
+
const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
|
|
20961
21158
|
return statusDto(row);
|
|
20962
21159
|
}
|
|
20963
21160
|
);
|
|
20964
21161
|
app.delete("/projects/:name/ads/connection", async (request) => {
|
|
20965
21162
|
const project = resolveProject(app.db, request.params.name);
|
|
20966
|
-
const row = app.db.select().from(adsConnections).where(
|
|
21163
|
+
const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
|
|
20967
21164
|
if (row) {
|
|
20968
21165
|
app.db.transaction((tx) => {
|
|
20969
|
-
tx.delete(adsConnections).where(
|
|
21166
|
+
tx.delete(adsConnections).where(eq22(adsConnections.id, row.id)).run();
|
|
20970
21167
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
20971
21168
|
projectId: project.id,
|
|
20972
21169
|
actor: "api",
|
|
@@ -20982,19 +21179,19 @@ async function adsRoutes(app, opts) {
|
|
|
20982
21179
|
});
|
|
20983
21180
|
app.get("/projects/:name/ads/status", async (request) => {
|
|
20984
21181
|
const project = resolveProject(app.db, request.params.name);
|
|
20985
|
-
const row = app.db.select().from(adsConnections).where(
|
|
21182
|
+
const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
|
|
20986
21183
|
return statusDto(row);
|
|
20987
21184
|
});
|
|
20988
21185
|
app.post("/projects/:name/ads/sync", async (request) => {
|
|
20989
21186
|
const project = resolveProject(app.db, request.params.name);
|
|
20990
|
-
const row = app.db.select().from(adsConnections).where(
|
|
21187
|
+
const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
|
|
20991
21188
|
if (!row) {
|
|
20992
21189
|
throw validationError('No ads connection for this project. Run "canonry ads connect" first.');
|
|
20993
21190
|
}
|
|
20994
|
-
const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(
|
|
20995
|
-
|
|
20996
|
-
|
|
20997
|
-
|
|
21191
|
+
const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and15(
|
|
21192
|
+
eq22(runs.projectId, project.id),
|
|
21193
|
+
eq22(runs.kind, RunKinds["ads-sync"]),
|
|
21194
|
+
inArray11(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
20998
21195
|
)).get();
|
|
20999
21196
|
if (inFlight) {
|
|
21000
21197
|
const existing = { runId: inFlight.id, status: inFlight.status };
|
|
@@ -21015,9 +21212,9 @@ async function adsRoutes(app, opts) {
|
|
|
21015
21212
|
});
|
|
21016
21213
|
app.get("/projects/:name/ads/campaigns", async (request) => {
|
|
21017
21214
|
const project = resolveProject(app.db, request.params.name);
|
|
21018
|
-
const campaignRows = app.db.select().from(adsCampaigns).where(
|
|
21019
|
-
const groupRows = app.db.select().from(adsAdGroups).where(
|
|
21020
|
-
const adRows = app.db.select().from(adsAds).where(
|
|
21215
|
+
const campaignRows = app.db.select().from(adsCampaigns).where(eq22(adsCampaigns.projectId, project.id)).all();
|
|
21216
|
+
const groupRows = app.db.select().from(adsAdGroups).where(eq22(adsAdGroups.projectId, project.id)).all();
|
|
21217
|
+
const adRows = app.db.select().from(adsAds).where(eq22(adsAds.projectId, project.id)).all();
|
|
21021
21218
|
const adsByGroup = /* @__PURE__ */ new Map();
|
|
21022
21219
|
for (const ad of adRows) {
|
|
21023
21220
|
const dto = {
|
|
@@ -21071,12 +21268,12 @@ async function adsRoutes(app, opts) {
|
|
|
21071
21268
|
}
|
|
21072
21269
|
parsedLevel = result.data;
|
|
21073
21270
|
}
|
|
21074
|
-
const conditions = [
|
|
21075
|
-
if (parsedLevel) conditions.push(
|
|
21076
|
-
if (entityId) conditions.push(
|
|
21271
|
+
const conditions = [eq22(adsInsightsDaily.projectId, project.id)];
|
|
21272
|
+
if (parsedLevel) conditions.push(eq22(adsInsightsDaily.level, parsedLevel));
|
|
21273
|
+
if (entityId) conditions.push(eq22(adsInsightsDaily.entityId, entityId));
|
|
21077
21274
|
if (from) conditions.push(gte3(adsInsightsDaily.date, from));
|
|
21078
21275
|
if (to) conditions.push(lte2(adsInsightsDaily.date, to));
|
|
21079
|
-
const rows = app.db.select().from(adsInsightsDaily).where(
|
|
21276
|
+
const rows = app.db.select().from(adsInsightsDaily).where(and15(...conditions)).orderBy(asc2(adsInsightsDaily.date)).all();
|
|
21080
21277
|
const dtoRows = rows.map((row) => ({
|
|
21081
21278
|
level: row.level,
|
|
21082
21279
|
entityId: row.entityId,
|
|
@@ -21087,19 +21284,19 @@ async function adsRoutes(app, opts) {
|
|
|
21087
21284
|
ctr: adsCtr(row.clicks, row.impressions),
|
|
21088
21285
|
cpcMicros: adsCpcMicros(row.spendMicros, row.clicks)
|
|
21089
21286
|
}));
|
|
21090
|
-
const conn = app.db.select().from(adsConnections).where(
|
|
21287
|
+
const conn = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
|
|
21091
21288
|
const response = { rows: dtoRows, currencyCode: conn?.currencyCode ?? null };
|
|
21092
21289
|
return response;
|
|
21093
21290
|
});
|
|
21094
21291
|
app.get("/projects/:name/ads/summary", async (request) => {
|
|
21095
21292
|
const project = resolveProject(app.db, request.params.name);
|
|
21096
|
-
const row = app.db.select().from(adsConnections).where(
|
|
21097
|
-
const campaignCount = app.db.select().from(adsCampaigns).where(
|
|
21098
|
-
const adGroupCount = app.db.select().from(adsAdGroups).where(
|
|
21099
|
-
const adCount = app.db.select().from(adsAds).where(
|
|
21100
|
-
const campaignInsights = app.db.select().from(adsInsightsDaily).where(
|
|
21101
|
-
|
|
21102
|
-
|
|
21293
|
+
const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
|
|
21294
|
+
const campaignCount = app.db.select().from(adsCampaigns).where(eq22(adsCampaigns.projectId, project.id)).all().length;
|
|
21295
|
+
const adGroupCount = app.db.select().from(adsAdGroups).where(eq22(adsAdGroups.projectId, project.id)).all().length;
|
|
21296
|
+
const adCount = app.db.select().from(adsAds).where(eq22(adsAds.projectId, project.id)).all().length;
|
|
21297
|
+
const campaignInsights = app.db.select().from(adsInsightsDaily).where(and15(
|
|
21298
|
+
eq22(adsInsightsDaily.projectId, project.id),
|
|
21299
|
+
eq22(adsInsightsDaily.level, "campaign")
|
|
21103
21300
|
)).all();
|
|
21104
21301
|
let impressions = 0;
|
|
21105
21302
|
let clicks = 0;
|
|
@@ -21136,7 +21333,7 @@ async function adsRoutes(app, opts) {
|
|
|
21136
21333
|
|
|
21137
21334
|
// ../api-routes/src/bing.ts
|
|
21138
21335
|
import crypto19 from "crypto";
|
|
21139
|
-
import { eq as
|
|
21336
|
+
import { eq as eq23, and as and16, desc as desc12 } from "drizzle-orm";
|
|
21140
21337
|
|
|
21141
21338
|
// ../integration-bing/src/constants.ts
|
|
21142
21339
|
var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
@@ -21510,7 +21707,7 @@ async function bingRoutes(app, opts) {
|
|
|
21510
21707
|
const store = requireConnectionStore();
|
|
21511
21708
|
const project = resolveProject(app.db, request.params.name);
|
|
21512
21709
|
requireConnection(store, project.canonicalDomain);
|
|
21513
|
-
const allInspections = app.db.select().from(bingUrlInspections).where(
|
|
21710
|
+
const allInspections = app.db.select().from(bingUrlInspections).where(eq23(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
|
|
21514
21711
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
21515
21712
|
const definitiveByUrl = /* @__PURE__ */ new Map();
|
|
21516
21713
|
for (const row of allInspections) {
|
|
@@ -21599,7 +21796,7 @@ async function bingRoutes(app, opts) {
|
|
|
21599
21796
|
const project = resolveProject(app.db, request.params.name);
|
|
21600
21797
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
21601
21798
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
21602
|
-
const rows = app.db.select().from(bingCoverageSnapshots).where(
|
|
21799
|
+
const rows = app.db.select().from(bingCoverageSnapshots).where(eq23(bingCoverageSnapshots.projectId, project.id)).orderBy(desc12(bingCoverageSnapshots.date)).limit(limit).all();
|
|
21603
21800
|
return rows.map((r) => ({
|
|
21604
21801
|
date: r.date,
|
|
21605
21802
|
indexed: r.indexed,
|
|
@@ -21611,8 +21808,8 @@ async function bingRoutes(app, opts) {
|
|
|
21611
21808
|
requireConnectionStore();
|
|
21612
21809
|
const project = resolveProject(app.db, request.params.name);
|
|
21613
21810
|
const { url, limit } = request.query;
|
|
21614
|
-
const whereClause = url ?
|
|
21615
|
-
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(
|
|
21811
|
+
const whereClause = url ? and16(eq23(bingUrlInspections.projectId, project.id), eq23(bingUrlInspections.url, url)) : eq23(bingUrlInspections.projectId, project.id);
|
|
21812
|
+
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc12(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
|
|
21616
21813
|
return filtered.map((r) => ({
|
|
21617
21814
|
id: r.id,
|
|
21618
21815
|
url: r.url,
|
|
@@ -21701,7 +21898,7 @@ async function bingRoutes(app, opts) {
|
|
|
21701
21898
|
anchorCount: result.AnchorCount ?? null,
|
|
21702
21899
|
discoveryDate
|
|
21703
21900
|
}).run();
|
|
21704
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(
|
|
21901
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq23(runs.id, runId)).run();
|
|
21705
21902
|
return {
|
|
21706
21903
|
id,
|
|
21707
21904
|
url,
|
|
@@ -21717,7 +21914,7 @@ async function bingRoutes(app, opts) {
|
|
|
21717
21914
|
} catch (e) {
|
|
21718
21915
|
const msg = e instanceof Error ? e.message : String(e);
|
|
21719
21916
|
bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
|
|
21720
|
-
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
21917
|
+
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(runs.id, runId)).run();
|
|
21721
21918
|
throw e;
|
|
21722
21919
|
}
|
|
21723
21920
|
});
|
|
@@ -21744,7 +21941,7 @@ async function bingRoutes(app, opts) {
|
|
|
21744
21941
|
} else {
|
|
21745
21942
|
bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
|
|
21746
21943
|
}
|
|
21747
|
-
const run = app.db.select().from(runs).where(
|
|
21944
|
+
const run = app.db.select().from(runs).where(eq23(runs.id, runId)).get();
|
|
21748
21945
|
return run;
|
|
21749
21946
|
});
|
|
21750
21947
|
app.post("/projects/:name/bing/request-indexing", async (request) => {
|
|
@@ -21756,7 +21953,7 @@ async function bingRoutes(app, opts) {
|
|
|
21756
21953
|
}
|
|
21757
21954
|
let urlsToSubmit = request.body?.urls ?? [];
|
|
21758
21955
|
if (request.body?.allUnindexed) {
|
|
21759
|
-
const allInspections = app.db.select().from(bingUrlInspections).where(
|
|
21956
|
+
const allInspections = app.db.select().from(bingUrlInspections).where(eq23(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
|
|
21760
21957
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
21761
21958
|
for (const row of allInspections) {
|
|
21762
21959
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -21843,14 +22040,14 @@ async function bingRoutes(app, opts) {
|
|
|
21843
22040
|
import fs from "fs";
|
|
21844
22041
|
import path from "path";
|
|
21845
22042
|
import os from "os";
|
|
21846
|
-
import { eq as
|
|
22043
|
+
import { eq as eq24, and as and17 } from "drizzle-orm";
|
|
21847
22044
|
function getScreenshotDir() {
|
|
21848
22045
|
return path.join(os.homedir(), ".canonry", "screenshots");
|
|
21849
22046
|
}
|
|
21850
22047
|
async function cdpRoutes(app, opts) {
|
|
21851
22048
|
app.get("/screenshots/:snapshotId", async (request, reply) => {
|
|
21852
22049
|
const { snapshotId } = request.params;
|
|
21853
|
-
const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(
|
|
22050
|
+
const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(eq24(querySnapshots.id, snapshotId)).get();
|
|
21854
22051
|
if (!snapshot?.screenshotPath) {
|
|
21855
22052
|
const err = notFound("Screenshot", snapshotId);
|
|
21856
22053
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -21917,7 +22114,7 @@ async function cdpRoutes(app, opts) {
|
|
|
21917
22114
|
async (request, reply) => {
|
|
21918
22115
|
const project = resolveProject(app.db, request.params.name);
|
|
21919
22116
|
const { runId } = request.params;
|
|
21920
|
-
const run = app.db.select().from(runs).where(
|
|
22117
|
+
const run = app.db.select().from(runs).where(and17(eq24(runs.id, runId), eq24(runs.projectId, project.id))).get();
|
|
21921
22118
|
if (!run) {
|
|
21922
22119
|
const err = notFound("Run", runId);
|
|
21923
22120
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -21930,8 +22127,8 @@ async function cdpRoutes(app, opts) {
|
|
|
21930
22127
|
citedDomains: querySnapshots.citedDomains,
|
|
21931
22128
|
screenshotPath: querySnapshots.screenshotPath,
|
|
21932
22129
|
rawResponse: querySnapshots.rawResponse
|
|
21933
|
-
}).from(querySnapshots).where(
|
|
21934
|
-
const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(
|
|
22130
|
+
}).from(querySnapshots).where(eq24(querySnapshots.runId, runId)).all());
|
|
22131
|
+
const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq24(queries.projectId, project.id)).all();
|
|
21935
22132
|
const queryMap = new Map(queryRows.map((q) => [q.id, q.query]));
|
|
21936
22133
|
const byQuery = /* @__PURE__ */ new Map();
|
|
21937
22134
|
for (const snap of snapshots) {
|
|
@@ -22014,7 +22211,7 @@ async function cdpRoutes(app, opts) {
|
|
|
22014
22211
|
|
|
22015
22212
|
// ../api-routes/src/ga.ts
|
|
22016
22213
|
import crypto20 from "crypto";
|
|
22017
|
-
import { eq as
|
|
22214
|
+
import { eq as eq25, desc as desc13, and as and18, sql as sql9 } from "drizzle-orm";
|
|
22018
22215
|
function gaLog(level, action, ctx) {
|
|
22019
22216
|
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
|
|
22020
22217
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
@@ -22221,10 +22418,10 @@ async function ga4Routes(app, opts) {
|
|
|
22221
22418
|
if (!saConn && !oauthConn) {
|
|
22222
22419
|
throw notFound("GA4 connection", project.name);
|
|
22223
22420
|
}
|
|
22224
|
-
app.db.delete(gaTrafficSnapshots).where(
|
|
22225
|
-
app.db.delete(gaTrafficSummaries).where(
|
|
22226
|
-
app.db.delete(gaAiReferrals).where(
|
|
22227
|
-
app.db.delete(gaSocialReferrals).where(
|
|
22421
|
+
app.db.delete(gaTrafficSnapshots).where(eq25(gaTrafficSnapshots.projectId, project.id)).run();
|
|
22422
|
+
app.db.delete(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).run();
|
|
22423
|
+
app.db.delete(gaAiReferrals).where(eq25(gaAiReferrals.projectId, project.id)).run();
|
|
22424
|
+
app.db.delete(gaSocialReferrals).where(eq25(gaSocialReferrals.projectId, project.id)).run();
|
|
22228
22425
|
const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
|
|
22229
22426
|
opts.ga4CredentialStore?.deleteConnection(project.name);
|
|
22230
22427
|
opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
|
|
@@ -22245,7 +22442,7 @@ async function ga4Routes(app, opts) {
|
|
|
22245
22442
|
if (!connected) {
|
|
22246
22443
|
return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
|
|
22247
22444
|
}
|
|
22248
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
22445
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
22249
22446
|
return {
|
|
22250
22447
|
connected: true,
|
|
22251
22448
|
propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
|
|
@@ -22309,8 +22506,8 @@ async function ga4Routes(app, opts) {
|
|
|
22309
22506
|
app.db.transaction((tx) => {
|
|
22310
22507
|
if (syncTraffic) {
|
|
22311
22508
|
tx.delete(gaTrafficSnapshots).where(
|
|
22312
|
-
|
|
22313
|
-
|
|
22509
|
+
and18(
|
|
22510
|
+
eq25(gaTrafficSnapshots.projectId, project.id),
|
|
22314
22511
|
sql9`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
|
|
22315
22512
|
sql9`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
|
|
22316
22513
|
)
|
|
@@ -22333,8 +22530,8 @@ async function ga4Routes(app, opts) {
|
|
|
22333
22530
|
}
|
|
22334
22531
|
if (syncAi) {
|
|
22335
22532
|
tx.delete(gaAiReferrals).where(
|
|
22336
|
-
|
|
22337
|
-
|
|
22533
|
+
and18(
|
|
22534
|
+
eq25(gaAiReferrals.projectId, project.id),
|
|
22338
22535
|
sql9`${gaAiReferrals.date} >= ${summary.periodStart}`,
|
|
22339
22536
|
sql9`${gaAiReferrals.date} <= ${summary.periodEnd}`
|
|
22340
22537
|
)
|
|
@@ -22359,8 +22556,8 @@ async function ga4Routes(app, opts) {
|
|
|
22359
22556
|
}
|
|
22360
22557
|
if (syncSocial) {
|
|
22361
22558
|
tx.delete(gaSocialReferrals).where(
|
|
22362
|
-
|
|
22363
|
-
|
|
22559
|
+
and18(
|
|
22560
|
+
eq25(gaSocialReferrals.projectId, project.id),
|
|
22364
22561
|
sql9`${gaSocialReferrals.date} >= ${summary.periodStart}`,
|
|
22365
22562
|
sql9`${gaSocialReferrals.date} <= ${summary.periodEnd}`
|
|
22366
22563
|
)
|
|
@@ -22381,7 +22578,7 @@ async function ga4Routes(app, opts) {
|
|
|
22381
22578
|
}
|
|
22382
22579
|
}
|
|
22383
22580
|
if (syncSummary) {
|
|
22384
|
-
tx.delete(gaTrafficSummaries).where(
|
|
22581
|
+
tx.delete(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).run();
|
|
22385
22582
|
tx.insert(gaTrafficSummaries).values({
|
|
22386
22583
|
id: crypto20.randomUUID(),
|
|
22387
22584
|
projectId: project.id,
|
|
@@ -22393,7 +22590,7 @@ async function ga4Routes(app, opts) {
|
|
|
22393
22590
|
syncedAt: now,
|
|
22394
22591
|
syncRunId: runId
|
|
22395
22592
|
}).run();
|
|
22396
|
-
tx.delete(gaTrafficWindowSummaries).where(
|
|
22593
|
+
tx.delete(gaTrafficWindowSummaries).where(eq25(gaTrafficWindowSummaries.projectId, project.id)).run();
|
|
22397
22594
|
for (const ws of windowSummaries) {
|
|
22398
22595
|
tx.insert(gaTrafficWindowSummaries).values({
|
|
22399
22596
|
id: crypto20.randomUUID(),
|
|
@@ -22411,7 +22608,7 @@ async function ga4Routes(app, opts) {
|
|
|
22411
22608
|
}
|
|
22412
22609
|
}
|
|
22413
22610
|
});
|
|
22414
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(
|
|
22611
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq25(runs.id, runId)).run();
|
|
22415
22612
|
const syncedComponents = only ? [
|
|
22416
22613
|
...syncTraffic ? ["traffic"] : [],
|
|
22417
22614
|
...syncSummary ? ["summary"] : [],
|
|
@@ -22440,7 +22637,7 @@ async function ga4Routes(app, opts) {
|
|
|
22440
22637
|
} catch (e) {
|
|
22441
22638
|
const msg = e instanceof Error ? e.message : String(e);
|
|
22442
22639
|
gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
|
|
22443
|
-
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
22640
|
+
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq25(runs.id, runId)).run();
|
|
22444
22641
|
throw e;
|
|
22445
22642
|
}
|
|
22446
22643
|
});
|
|
@@ -22451,11 +22648,11 @@ async function ga4Routes(app, opts) {
|
|
|
22451
22648
|
const window = parseWindow(request.query.window);
|
|
22452
22649
|
const cutoff = windowCutoff(window);
|
|
22453
22650
|
const cutoffDate = cutoff?.slice(0, 10) ?? null;
|
|
22454
|
-
const snapshotConditions = [
|
|
22651
|
+
const snapshotConditions = [eq25(gaTrafficSnapshots.projectId, project.id)];
|
|
22455
22652
|
if (cutoffDate) snapshotConditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
22456
|
-
const aiConditions = [
|
|
22653
|
+
const aiConditions = [eq25(gaAiReferrals.projectId, project.id)];
|
|
22457
22654
|
if (cutoffDate) aiConditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
22458
|
-
const socialConditions = [
|
|
22655
|
+
const socialConditions = [eq25(gaSocialReferrals.projectId, project.id)];
|
|
22459
22656
|
if (cutoffDate) socialConditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
22460
22657
|
const windowSummaryRow = cutoffDate ? app.db.select({
|
|
22461
22658
|
totalSessions: gaTrafficWindowSummaries.totalSessions,
|
|
@@ -22463,42 +22660,42 @@ async function ga4Routes(app, opts) {
|
|
|
22463
22660
|
totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
|
|
22464
22661
|
totalUsers: gaTrafficWindowSummaries.totalUsers
|
|
22465
22662
|
}).from(gaTrafficWindowSummaries).where(
|
|
22466
|
-
|
|
22467
|
-
|
|
22468
|
-
|
|
22663
|
+
and18(
|
|
22664
|
+
eq25(gaTrafficWindowSummaries.projectId, project.id),
|
|
22665
|
+
eq25(gaTrafficWindowSummaries.windowKey, window)
|
|
22469
22666
|
)
|
|
22470
22667
|
).get() : null;
|
|
22471
22668
|
const snapshotTotalsRow = cutoffDate && !windowSummaryRow ? app.db.select({
|
|
22472
22669
|
totalSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
|
|
22473
22670
|
totalOrganicSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
|
|
22474
22671
|
totalUsers: sql9`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
|
|
22475
|
-
}).from(gaTrafficSnapshots).where(
|
|
22672
|
+
}).from(gaTrafficSnapshots).where(and18(...snapshotConditions)).get() : null;
|
|
22476
22673
|
const summaryRow = cutoffDate ? windowSummaryRow ?? snapshotTotalsRow : app.db.select({
|
|
22477
22674
|
totalSessions: gaTrafficSummaries.totalSessions,
|
|
22478
22675
|
totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
|
|
22479
22676
|
totalUsers: gaTrafficSummaries.totalUsers
|
|
22480
|
-
}).from(gaTrafficSummaries).where(
|
|
22677
|
+
}).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).get();
|
|
22481
22678
|
const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
|
|
22482
22679
|
totalDirectSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
|
|
22483
|
-
}).from(gaTrafficSnapshots).where(
|
|
22680
|
+
}).from(gaTrafficSnapshots).where(and18(...snapshotConditions)).get();
|
|
22484
22681
|
const summaryMeta = app.db.select({
|
|
22485
22682
|
periodStart: gaTrafficSummaries.periodStart,
|
|
22486
22683
|
periodEnd: gaTrafficSummaries.periodEnd
|
|
22487
|
-
}).from(gaTrafficSummaries).where(
|
|
22684
|
+
}).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).get();
|
|
22488
22685
|
const rows = app.db.select({
|
|
22489
22686
|
landingPage: sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
|
|
22490
22687
|
sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
|
|
22491
22688
|
organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
22492
22689
|
directSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
|
|
22493
22690
|
users: sql9`SUM(${gaTrafficSnapshots.users})`
|
|
22494
|
-
}).from(gaTrafficSnapshots).where(
|
|
22691
|
+
}).from(gaTrafficSnapshots).where(and18(...snapshotConditions)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
|
|
22495
22692
|
const aiReferralRows = app.db.select({
|
|
22496
22693
|
source: gaAiReferrals.source,
|
|
22497
22694
|
medium: gaAiReferrals.medium,
|
|
22498
22695
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
22499
22696
|
sessions: sql9`SUM(${gaAiReferrals.sessions})`,
|
|
22500
22697
|
users: sql9`SUM(${gaAiReferrals.users})`
|
|
22501
|
-
}).from(gaAiReferrals).where(
|
|
22698
|
+
}).from(gaAiReferrals).where(and18(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
|
|
22502
22699
|
const aiReferralLandingPageRows = app.db.select({
|
|
22503
22700
|
source: gaAiReferrals.source,
|
|
22504
22701
|
medium: gaAiReferrals.medium,
|
|
@@ -22506,7 +22703,7 @@ async function ga4Routes(app, opts) {
|
|
|
22506
22703
|
landingPage: sql9`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
22507
22704
|
sessions: sql9`SUM(${gaAiReferrals.sessions})`,
|
|
22508
22705
|
users: sql9`SUM(${gaAiReferrals.users})`
|
|
22509
|
-
}).from(gaAiReferrals).where(
|
|
22706
|
+
}).from(gaAiReferrals).where(and18(...aiConditions)).groupBy(
|
|
22510
22707
|
gaAiReferrals.source,
|
|
22511
22708
|
gaAiReferrals.medium,
|
|
22512
22709
|
gaAiReferrals.sourceDimension,
|
|
@@ -22543,7 +22740,7 @@ async function ga4Routes(app, opts) {
|
|
|
22543
22740
|
channelGroup: gaAiReferrals.channelGroup,
|
|
22544
22741
|
sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
|
|
22545
22742
|
users: sql9`COALESCE(SUM(${gaAiReferrals.users}), 0)`
|
|
22546
|
-
}).from(gaAiReferrals).where(
|
|
22743
|
+
}).from(gaAiReferrals).where(and18(...aiConditions, eq25(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
|
|
22547
22744
|
const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
|
|
22548
22745
|
let aiBySessionUsers = 0;
|
|
22549
22746
|
for (const row of aiBySessionRows) {
|
|
@@ -22557,12 +22754,12 @@ async function ga4Routes(app, opts) {
|
|
|
22557
22754
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
22558
22755
|
sessions: sql9`SUM(${gaSocialReferrals.sessions})`,
|
|
22559
22756
|
users: sql9`SUM(${gaSocialReferrals.users})`
|
|
22560
|
-
}).from(gaSocialReferrals).where(
|
|
22757
|
+
}).from(gaSocialReferrals).where(and18(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql9`SUM(${gaSocialReferrals.sessions}) DESC`).all();
|
|
22561
22758
|
const socialTotals = app.db.select({
|
|
22562
22759
|
sessions: sql9`SUM(${gaSocialReferrals.sessions})`,
|
|
22563
22760
|
users: sql9`SUM(${gaSocialReferrals.users})`
|
|
22564
|
-
}).from(gaSocialReferrals).where(
|
|
22565
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
22761
|
+
}).from(gaSocialReferrals).where(and18(...socialConditions)).get();
|
|
22762
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
22566
22763
|
const total = summaryRow?.totalSessions ?? 0;
|
|
22567
22764
|
const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
|
|
22568
22765
|
const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
|
|
@@ -22642,7 +22839,7 @@ async function ga4Routes(app, opts) {
|
|
|
22642
22839
|
const project = resolveProject(app.db, request.params.name);
|
|
22643
22840
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
22644
22841
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
22645
|
-
const conditions = [
|
|
22842
|
+
const conditions = [eq25(gaAiReferrals.projectId, project.id)];
|
|
22646
22843
|
if (cutoffDate) conditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
22647
22844
|
const rows = app.db.select({
|
|
22648
22845
|
date: gaAiReferrals.date,
|
|
@@ -22652,7 +22849,7 @@ async function ga4Routes(app, opts) {
|
|
|
22652
22849
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
22653
22850
|
sessions: sql9`SUM(${gaAiReferrals.sessions})`,
|
|
22654
22851
|
users: sql9`SUM(${gaAiReferrals.users})`
|
|
22655
|
-
}).from(gaAiReferrals).where(
|
|
22852
|
+
}).from(gaAiReferrals).where(and18(...conditions)).groupBy(
|
|
22656
22853
|
gaAiReferrals.date,
|
|
22657
22854
|
gaAiReferrals.source,
|
|
22658
22855
|
gaAiReferrals.medium,
|
|
@@ -22665,7 +22862,7 @@ async function ga4Routes(app, opts) {
|
|
|
22665
22862
|
const project = resolveProject(app.db, request.params.name);
|
|
22666
22863
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
22667
22864
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
22668
|
-
const conditions = [
|
|
22865
|
+
const conditions = [eq25(gaSocialReferrals.projectId, project.id)];
|
|
22669
22866
|
if (cutoffDate) conditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
22670
22867
|
const rows = app.db.select({
|
|
22671
22868
|
date: gaSocialReferrals.date,
|
|
@@ -22674,7 +22871,7 @@ async function ga4Routes(app, opts) {
|
|
|
22674
22871
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
22675
22872
|
sessions: gaSocialReferrals.sessions,
|
|
22676
22873
|
users: gaSocialReferrals.users
|
|
22677
|
-
}).from(gaSocialReferrals).where(
|
|
22874
|
+
}).from(gaSocialReferrals).where(and18(...conditions)).orderBy(gaSocialReferrals.date).all();
|
|
22678
22875
|
return rows;
|
|
22679
22876
|
});
|
|
22680
22877
|
app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
|
|
@@ -22687,8 +22884,8 @@ async function ga4Routes(app, opts) {
|
|
|
22687
22884
|
d.setDate(d.getDate() - n);
|
|
22688
22885
|
return fmt(d);
|
|
22689
22886
|
};
|
|
22690
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
22691
|
-
|
|
22887
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and18(
|
|
22888
|
+
eq25(gaSocialReferrals.projectId, project.id),
|
|
22692
22889
|
sql9`${gaSocialReferrals.date} >= ${from}`,
|
|
22693
22890
|
sql9`${gaSocialReferrals.date} < ${to}`
|
|
22694
22891
|
)).get();
|
|
@@ -22700,16 +22897,16 @@ async function ga4Routes(app, opts) {
|
|
|
22700
22897
|
const sourceCurrent = app.db.select({
|
|
22701
22898
|
source: gaSocialReferrals.source,
|
|
22702
22899
|
sessions: sql9`SUM(${gaSocialReferrals.sessions})`
|
|
22703
|
-
}).from(gaSocialReferrals).where(
|
|
22704
|
-
|
|
22900
|
+
}).from(gaSocialReferrals).where(and18(
|
|
22901
|
+
eq25(gaSocialReferrals.projectId, project.id),
|
|
22705
22902
|
sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
|
|
22706
22903
|
sql9`${gaSocialReferrals.date} < ${fmt(today)}`
|
|
22707
22904
|
)).groupBy(gaSocialReferrals.source).all();
|
|
22708
22905
|
const sourcePrev = app.db.select({
|
|
22709
22906
|
source: gaSocialReferrals.source,
|
|
22710
22907
|
sessions: sql9`SUM(${gaSocialReferrals.sessions})`
|
|
22711
|
-
}).from(gaSocialReferrals).where(
|
|
22712
|
-
|
|
22908
|
+
}).from(gaSocialReferrals).where(and18(
|
|
22909
|
+
eq25(gaSocialReferrals.projectId, project.id),
|
|
22713
22910
|
sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
|
|
22714
22911
|
sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`
|
|
22715
22912
|
)).groupBy(gaSocialReferrals.source).all();
|
|
@@ -22750,16 +22947,16 @@ async function ga4Routes(app, opts) {
|
|
|
22750
22947
|
return fmt(d);
|
|
22751
22948
|
};
|
|
22752
22949
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
22753
|
-
const sumTotal = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
22754
|
-
const sumOrganic = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
22755
|
-
const sumDirect = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
22756
|
-
const sumAi = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
22757
|
-
|
|
22950
|
+
const sumTotal = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and18(eq25(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
22951
|
+
const sumOrganic = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and18(eq25(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
22952
|
+
const sumDirect = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and18(eq25(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
22953
|
+
const sumAi = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and18(
|
|
22954
|
+
eq25(gaAiReferrals.projectId, project.id),
|
|
22758
22955
|
sql9`${gaAiReferrals.date} >= ${from}`,
|
|
22759
22956
|
sql9`${gaAiReferrals.date} < ${to}`,
|
|
22760
|
-
|
|
22957
|
+
eq25(gaAiReferrals.sourceDimension, "session")
|
|
22761
22958
|
)).get();
|
|
22762
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
22959
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and18(eq25(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${from}`, sql9`${gaSocialReferrals.date} < ${to}`)).get();
|
|
22763
22960
|
const todayStr = fmt(today);
|
|
22764
22961
|
const buildTrend = (sum) => {
|
|
22765
22962
|
const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
|
|
@@ -22768,17 +22965,17 @@ async function ga4Routes(app, opts) {
|
|
|
22768
22965
|
const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
|
|
22769
22966
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
|
|
22770
22967
|
};
|
|
22771
|
-
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
22772
|
-
|
|
22968
|
+
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and18(
|
|
22969
|
+
eq25(gaAiReferrals.projectId, project.id),
|
|
22773
22970
|
sql9`${gaAiReferrals.date} >= ${daysAgo(7)}`,
|
|
22774
22971
|
sql9`${gaAiReferrals.date} < ${todayStr}`,
|
|
22775
|
-
|
|
22972
|
+
eq25(gaAiReferrals.sourceDimension, "session")
|
|
22776
22973
|
)).groupBy(gaAiReferrals.source).all();
|
|
22777
|
-
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
22778
|
-
|
|
22974
|
+
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and18(
|
|
22975
|
+
eq25(gaAiReferrals.projectId, project.id),
|
|
22779
22976
|
sql9`${gaAiReferrals.date} >= ${daysAgo(14)}`,
|
|
22780
22977
|
sql9`${gaAiReferrals.date} < ${daysAgo(7)}`,
|
|
22781
|
-
|
|
22978
|
+
eq25(gaAiReferrals.sourceDimension, "session")
|
|
22782
22979
|
)).groupBy(gaAiReferrals.source).all();
|
|
22783
22980
|
const findBiggestMover = (current, prev) => {
|
|
22784
22981
|
const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
|
|
@@ -22794,8 +22991,8 @@ async function ga4Routes(app, opts) {
|
|
|
22794
22991
|
}
|
|
22795
22992
|
return mover;
|
|
22796
22993
|
};
|
|
22797
|
-
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(
|
|
22798
|
-
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(
|
|
22994
|
+
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and18(eq25(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql9`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
|
|
22995
|
+
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and18(eq25(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
|
|
22799
22996
|
return {
|
|
22800
22997
|
total: buildTrend(sumTotal),
|
|
22801
22998
|
organic: buildTrend(sumOrganic),
|
|
@@ -22810,14 +23007,14 @@ async function ga4Routes(app, opts) {
|
|
|
22810
23007
|
const project = resolveProject(app.db, request.params.name);
|
|
22811
23008
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
22812
23009
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
22813
|
-
const conditions = [
|
|
23010
|
+
const conditions = [eq25(gaTrafficSnapshots.projectId, project.id)];
|
|
22814
23011
|
if (cutoffDate) conditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
22815
23012
|
const rows = app.db.select({
|
|
22816
23013
|
date: gaTrafficSnapshots.date,
|
|
22817
23014
|
sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
|
|
22818
23015
|
organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
22819
23016
|
users: sql9`SUM(${gaTrafficSnapshots.users})`
|
|
22820
|
-
}).from(gaTrafficSnapshots).where(
|
|
23017
|
+
}).from(gaTrafficSnapshots).where(and18(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
22821
23018
|
return rows.map((r) => ({
|
|
22822
23019
|
date: r.date,
|
|
22823
23020
|
sessions: r.sessions ?? 0,
|
|
@@ -22833,7 +23030,7 @@ async function ga4Routes(app, opts) {
|
|
|
22833
23030
|
sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
|
|
22834
23031
|
organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
22835
23032
|
users: sql9`SUM(${gaTrafficSnapshots.users})`
|
|
22836
|
-
}).from(gaTrafficSnapshots).where(
|
|
23033
|
+
}).from(gaTrafficSnapshots).where(eq25(gaTrafficSnapshots.projectId, project.id)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
|
|
22837
23034
|
return {
|
|
22838
23035
|
pages: trafficPages.map((r) => ({
|
|
22839
23036
|
landingPage: r.landingPage,
|
|
@@ -24481,7 +24678,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
24481
24678
|
|
|
24482
24679
|
// ../api-routes/src/backlinks.ts
|
|
24483
24680
|
import crypto22 from "crypto";
|
|
24484
|
-
import { and as
|
|
24681
|
+
import { and as and20, asc as asc3, desc as desc14, eq as eq26, sql as sql10 } from "drizzle-orm";
|
|
24485
24682
|
|
|
24486
24683
|
// ../integration-commoncrawl/src/constants.ts
|
|
24487
24684
|
import os2 from "os";
|
|
@@ -24884,7 +25081,7 @@ function pruneCachedRelease(release, opts = {}) {
|
|
|
24884
25081
|
}
|
|
24885
25082
|
|
|
24886
25083
|
// ../api-routes/src/backlinks-filter.ts
|
|
24887
|
-
import { and as
|
|
25084
|
+
import { and as and19, ne as ne3, notLike } from "drizzle-orm";
|
|
24888
25085
|
var BACKLINK_FILTER_PATTERNS = [
|
|
24889
25086
|
"*.google.com",
|
|
24890
25087
|
"*.googleusercontent.com",
|
|
@@ -24907,7 +25104,7 @@ function backlinkCrawlerExclusionClause() {
|
|
|
24907
25104
|
conditions.push(ne3(backlinkDomains.linkingDomain, pattern));
|
|
24908
25105
|
}
|
|
24909
25106
|
}
|
|
24910
|
-
const combined =
|
|
25107
|
+
const combined = and19(...conditions);
|
|
24911
25108
|
if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
|
|
24912
25109
|
return combined;
|
|
24913
25110
|
}
|
|
@@ -24978,11 +25175,11 @@ function mapRunRow(row) {
|
|
|
24978
25175
|
}
|
|
24979
25176
|
function latestSummaryForProject(db, projectId, source, release) {
|
|
24980
25177
|
const conditions = [
|
|
24981
|
-
|
|
24982
|
-
|
|
25178
|
+
eq26(backlinkSummaries.projectId, projectId),
|
|
25179
|
+
eq26(backlinkSummaries.source, source)
|
|
24983
25180
|
];
|
|
24984
|
-
if (release) conditions.push(
|
|
24985
|
-
return db.select().from(backlinkSummaries).where(
|
|
25181
|
+
if (release) conditions.push(eq26(backlinkSummaries.release, release));
|
|
25182
|
+
return db.select().from(backlinkSummaries).where(and20(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
|
|
24986
25183
|
}
|
|
24987
25184
|
function parseExcludeCrawlers(value) {
|
|
24988
25185
|
if (!value) return false;
|
|
@@ -24990,12 +25187,12 @@ function parseExcludeCrawlers(value) {
|
|
|
24990
25187
|
return lower === "1" || lower === "true" || lower === "yes";
|
|
24991
25188
|
}
|
|
24992
25189
|
function computeFilteredSummary(db, base) {
|
|
24993
|
-
const baseDomainCondition =
|
|
24994
|
-
|
|
24995
|
-
|
|
24996
|
-
|
|
25190
|
+
const baseDomainCondition = and20(
|
|
25191
|
+
eq26(backlinkDomains.projectId, base.projectId),
|
|
25192
|
+
eq26(backlinkDomains.source, base.source),
|
|
25193
|
+
eq26(backlinkDomains.release, base.release)
|
|
24997
25194
|
);
|
|
24998
|
-
const filteredCondition =
|
|
25195
|
+
const filteredCondition = and20(baseDomainCondition, backlinkCrawlerExclusionClause());
|
|
24999
25196
|
const unfilteredAgg = db.select({
|
|
25000
25197
|
count: sql10`count(*)`,
|
|
25001
25198
|
total: sql10`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
@@ -25004,7 +25201,7 @@ function computeFilteredSummary(db, base) {
|
|
|
25004
25201
|
count: sql10`count(*)`,
|
|
25005
25202
|
total: sql10`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
25006
25203
|
}).from(backlinkDomains).where(filteredCondition).get();
|
|
25007
|
-
const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(
|
|
25204
|
+
const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(desc14(backlinkDomains.numHosts)).limit(10).all();
|
|
25008
25205
|
const totalLinkingDomains = Number(filteredAgg?.count ?? 0);
|
|
25009
25206
|
const totalHosts = Number(filteredAgg?.total ?? 0);
|
|
25010
25207
|
const unfilteredLinkingDomains = Number(unfilteredAgg?.count ?? 0);
|
|
@@ -25025,13 +25222,13 @@ function computeFilteredSummary(db, base) {
|
|
|
25025
25222
|
};
|
|
25026
25223
|
}
|
|
25027
25224
|
function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
|
|
25028
|
-
const summary = db.select().from(backlinkSummaries).where(
|
|
25225
|
+
const summary = db.select().from(backlinkSummaries).where(and20(eq26(backlinkSummaries.projectId, projectId), eq26(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
|
|
25029
25226
|
let totalLinkingDomains = summary?.totalLinkingDomains ?? 0;
|
|
25030
25227
|
if (summary && excludeCrawlers) {
|
|
25031
|
-
const filtered = db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(
|
|
25032
|
-
|
|
25033
|
-
|
|
25034
|
-
|
|
25228
|
+
const filtered = db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(and20(
|
|
25229
|
+
eq26(backlinkDomains.projectId, projectId),
|
|
25230
|
+
eq26(backlinkDomains.source, source),
|
|
25231
|
+
eq26(backlinkDomains.release, summary.release),
|
|
25035
25232
|
backlinkCrawlerExclusionClause()
|
|
25036
25233
|
)).get();
|
|
25037
25234
|
totalLinkingDomains = Number(filtered?.count ?? 0);
|
|
@@ -25046,7 +25243,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
|
|
|
25046
25243
|
};
|
|
25047
25244
|
}
|
|
25048
25245
|
function computeSourceAvailability(db, project, bingStore, excludeCrawlers) {
|
|
25049
|
-
const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(
|
|
25246
|
+
const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
|
|
25050
25247
|
const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
|
|
25051
25248
|
const bingConnected = !!bingStore?.getConnection(project.canonicalDomain);
|
|
25052
25249
|
const sources = [
|
|
@@ -25102,7 +25299,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
25102
25299
|
"@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
|
|
25103
25300
|
);
|
|
25104
25301
|
}
|
|
25105
|
-
const existing = app.db.select().from(ccReleaseSyncs).where(
|
|
25302
|
+
const existing = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.release, release)).get();
|
|
25106
25303
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25107
25304
|
if (existing) {
|
|
25108
25305
|
if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
|
|
@@ -25113,9 +25310,9 @@ async function backlinksRoutes(app, opts) {
|
|
|
25113
25310
|
phaseDetail: null,
|
|
25114
25311
|
error: null,
|
|
25115
25312
|
updatedAt: now
|
|
25116
|
-
}).where(
|
|
25313
|
+
}).where(eq26(ccReleaseSyncs.id, existing.id)).run();
|
|
25117
25314
|
opts.onReleaseSyncRequested(existing.id, release);
|
|
25118
|
-
const refreshed = app.db.select().from(ccReleaseSyncs).where(
|
|
25315
|
+
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.id, existing.id)).get();
|
|
25119
25316
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
25120
25317
|
}
|
|
25121
25318
|
const id = crypto22.randomUUID();
|
|
@@ -25127,15 +25324,15 @@ async function backlinksRoutes(app, opts) {
|
|
|
25127
25324
|
updatedAt: now
|
|
25128
25325
|
}).run();
|
|
25129
25326
|
opts.onReleaseSyncRequested(id, release);
|
|
25130
|
-
const inserted = app.db.select().from(ccReleaseSyncs).where(
|
|
25327
|
+
const inserted = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.id, id)).get();
|
|
25131
25328
|
return reply.status(201).send(mapSyncRow(inserted));
|
|
25132
25329
|
});
|
|
25133
25330
|
app.get("/backlinks/syncs/latest", async (_request, reply) => {
|
|
25134
|
-
const row = app.db.select().from(ccReleaseSyncs).orderBy(
|
|
25331
|
+
const row = app.db.select().from(ccReleaseSyncs).orderBy(desc14(ccReleaseSyncs.updatedAt)).limit(1).get();
|
|
25135
25332
|
return reply.send(row ? mapSyncRow(row) : null);
|
|
25136
25333
|
});
|
|
25137
25334
|
app.get("/backlinks/syncs", async (_request, reply) => {
|
|
25138
|
-
const rows = app.db.select().from(ccReleaseSyncs).orderBy(
|
|
25335
|
+
const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc14(ccReleaseSyncs.updatedAt)).all();
|
|
25139
25336
|
return reply.send(rows.map(mapSyncRow));
|
|
25140
25337
|
});
|
|
25141
25338
|
app.get("/backlinks/releases", async (_request, reply) => {
|
|
@@ -25185,7 +25382,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
25185
25382
|
createdAt: now
|
|
25186
25383
|
}).run();
|
|
25187
25384
|
opts.onBacklinkExtractRequested(runId, project.id, release);
|
|
25188
|
-
const run = app.db.select().from(runs).where(
|
|
25385
|
+
const run = app.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
25189
25386
|
return reply.status(201).send(mapRunRow(run));
|
|
25190
25387
|
});
|
|
25191
25388
|
app.get(
|
|
@@ -25211,18 +25408,18 @@ async function backlinksRoutes(app, opts) {
|
|
|
25211
25408
|
const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
|
|
25212
25409
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
25213
25410
|
const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
|
|
25214
|
-
const baseDomainCondition =
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25411
|
+
const baseDomainCondition = and20(
|
|
25412
|
+
eq26(backlinkDomains.projectId, project.id),
|
|
25413
|
+
eq26(backlinkDomains.source, source),
|
|
25414
|
+
eq26(backlinkDomains.release, targetRelease)
|
|
25218
25415
|
);
|
|
25219
|
-
const domainCondition = excludeCrawlers ?
|
|
25416
|
+
const domainCondition = excludeCrawlers ? and20(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
|
|
25220
25417
|
const totalRow = app.db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
25221
25418
|
const rows = app.db.select({
|
|
25222
25419
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
25223
25420
|
numHosts: backlinkDomains.numHosts,
|
|
25224
25421
|
source: backlinkDomains.source
|
|
25225
|
-
}).from(backlinkDomains).where(domainCondition).orderBy(
|
|
25422
|
+
}).from(backlinkDomains).where(domainCondition).orderBy(desc14(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
|
|
25226
25423
|
let summary = null;
|
|
25227
25424
|
if (summaryRow) {
|
|
25228
25425
|
summary = excludeCrawlers ? computeFilteredSummary(app.db, summaryRow) : mapSummaryRow(summaryRow);
|
|
@@ -25240,7 +25437,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
25240
25437
|
async (request, reply) => {
|
|
25241
25438
|
const project = resolveProject(app.db, request.params.name);
|
|
25242
25439
|
const source = parseSourceParam(request.query.source);
|
|
25243
|
-
const rows = app.db.select().from(backlinkSummaries).where(
|
|
25440
|
+
const rows = app.db.select().from(backlinkSummaries).where(and20(eq26(backlinkSummaries.projectId, project.id), eq26(backlinkSummaries.source, source))).orderBy(asc3(backlinkSummaries.queriedAt)).all();
|
|
25244
25441
|
const response = rows.map((r) => ({
|
|
25245
25442
|
release: r.release,
|
|
25246
25443
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -25287,7 +25484,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
25287
25484
|
createdAt: now
|
|
25288
25485
|
}).run();
|
|
25289
25486
|
opts.onBingBacklinkSyncRequested(runId, project.id);
|
|
25290
|
-
const run = app.db.select().from(runs).where(
|
|
25487
|
+
const run = app.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
25291
25488
|
return reply.status(201).send(mapRunRow(run));
|
|
25292
25489
|
}
|
|
25293
25490
|
);
|
|
@@ -25296,7 +25493,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
25296
25493
|
// ../api-routes/src/traffic.ts
|
|
25297
25494
|
import crypto24 from "crypto";
|
|
25298
25495
|
import { Agent as UndiciAgent } from "undici";
|
|
25299
|
-
import { and as
|
|
25496
|
+
import { and as and21, desc as desc15, eq as eq27, gte as gte4, lte as lte3, sql as sql11 } from "drizzle-orm";
|
|
25300
25497
|
|
|
25301
25498
|
// ../integration-cloud-run/src/auth.ts
|
|
25302
25499
|
import crypto23 from "crypto";
|
|
@@ -29113,8 +29310,8 @@ async function runBackfillTask(options) {
|
|
|
29113
29310
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
29114
29311
|
try {
|
|
29115
29312
|
app.db.transaction((tx) => {
|
|
29116
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
29117
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
29313
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq27(runs.id, runId)).run();
|
|
29314
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq27(trafficSources.id, sourceRow.id)).run();
|
|
29118
29315
|
});
|
|
29119
29316
|
} catch {
|
|
29120
29317
|
}
|
|
@@ -29129,7 +29326,7 @@ async function runBackfillTask(options) {
|
|
|
29129
29326
|
if (allEvents.length === 0) {
|
|
29130
29327
|
const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
29131
29328
|
try {
|
|
29132
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(
|
|
29329
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq27(runs.id, runId)).run();
|
|
29133
29330
|
} catch {
|
|
29134
29331
|
}
|
|
29135
29332
|
return;
|
|
@@ -29151,29 +29348,29 @@ async function runBackfillTask(options) {
|
|
|
29151
29348
|
try {
|
|
29152
29349
|
app.db.transaction((tx) => {
|
|
29153
29350
|
tx.delete(crawlerEventsHourly).where(
|
|
29154
|
-
|
|
29155
|
-
|
|
29351
|
+
and21(
|
|
29352
|
+
eq27(crawlerEventsHourly.sourceId, sourceRow.id),
|
|
29156
29353
|
gte4(crawlerEventsHourly.tsHour, windowStartIso),
|
|
29157
29354
|
lte3(crawlerEventsHourly.tsHour, windowEndIso)
|
|
29158
29355
|
)
|
|
29159
29356
|
).run();
|
|
29160
29357
|
tx.delete(aiUserFetchEventsHourly).where(
|
|
29161
|
-
|
|
29162
|
-
|
|
29358
|
+
and21(
|
|
29359
|
+
eq27(aiUserFetchEventsHourly.sourceId, sourceRow.id),
|
|
29163
29360
|
gte4(aiUserFetchEventsHourly.tsHour, windowStartIso),
|
|
29164
29361
|
lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
|
|
29165
29362
|
)
|
|
29166
29363
|
).run();
|
|
29167
29364
|
tx.delete(aiReferralEventsHourly).where(
|
|
29168
|
-
|
|
29169
|
-
|
|
29365
|
+
and21(
|
|
29366
|
+
eq27(aiReferralEventsHourly.sourceId, sourceRow.id),
|
|
29170
29367
|
gte4(aiReferralEventsHourly.tsHour, windowStartIso),
|
|
29171
29368
|
lte3(aiReferralEventsHourly.tsHour, windowEndIso)
|
|
29172
29369
|
)
|
|
29173
29370
|
).run();
|
|
29174
29371
|
tx.delete(rawEventSamples).where(
|
|
29175
|
-
|
|
29176
|
-
|
|
29372
|
+
and21(
|
|
29373
|
+
eq27(rawEventSamples.sourceId, sourceRow.id),
|
|
29177
29374
|
gte4(rawEventSamples.ts, windowStartIso),
|
|
29178
29375
|
lte3(rawEventSamples.ts, windowEndIso)
|
|
29179
29376
|
)
|
|
@@ -29262,8 +29459,8 @@ async function runBackfillTask(options) {
|
|
|
29262
29459
|
lastError: null,
|
|
29263
29460
|
lastEventIds: newRingBuffer,
|
|
29264
29461
|
updatedAt: finishedAt
|
|
29265
|
-
}).where(
|
|
29266
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
29462
|
+
}).where(eq27(trafficSources.id, sourceRow.id)).run();
|
|
29463
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq27(runs.id, runId)).run();
|
|
29267
29464
|
});
|
|
29268
29465
|
} catch (e) {
|
|
29269
29466
|
markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -29345,7 +29542,7 @@ async function trafficRoutes(app, opts) {
|
|
|
29345
29542
|
createdAt: existing?.createdAt ?? now,
|
|
29346
29543
|
updatedAt: now
|
|
29347
29544
|
});
|
|
29348
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
29545
|
+
const activeSource = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
|
|
29349
29546
|
const config = {
|
|
29350
29547
|
gcpProjectId,
|
|
29351
29548
|
serviceName: serviceName ?? null,
|
|
@@ -29361,8 +29558,8 @@ async function trafficRoutes(app, opts) {
|
|
|
29361
29558
|
lastError: null,
|
|
29362
29559
|
configJson: config,
|
|
29363
29560
|
updatedAt: now
|
|
29364
|
-
}).where(
|
|
29365
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
29561
|
+
}).where(eq27(trafficSources.id, activeSource.id)).run();
|
|
29562
|
+
sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
|
|
29366
29563
|
} else {
|
|
29367
29564
|
const newId = crypto24.randomUUID();
|
|
29368
29565
|
app.db.insert(trafficSources).values({
|
|
@@ -29379,7 +29576,7 @@ async function trafficRoutes(app, opts) {
|
|
|
29379
29576
|
createdAt: now,
|
|
29380
29577
|
updatedAt: now
|
|
29381
29578
|
}).run();
|
|
29382
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
29579
|
+
sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, newId)).get();
|
|
29383
29580
|
}
|
|
29384
29581
|
writeAuditLog(app.db, {
|
|
29385
29582
|
projectId: project.id,
|
|
@@ -29431,7 +29628,7 @@ async function trafficRoutes(app, opts) {
|
|
|
29431
29628
|
createdAt: existing?.createdAt ?? now,
|
|
29432
29629
|
updatedAt: now
|
|
29433
29630
|
});
|
|
29434
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
29631
|
+
const activeSource = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
|
|
29435
29632
|
const config = { baseUrl, username };
|
|
29436
29633
|
const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
|
|
29437
29634
|
let sourceRow;
|
|
@@ -29442,8 +29639,8 @@ async function trafficRoutes(app, opts) {
|
|
|
29442
29639
|
lastError: null,
|
|
29443
29640
|
configJson: config,
|
|
29444
29641
|
updatedAt: now
|
|
29445
|
-
}).where(
|
|
29446
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
29642
|
+
}).where(eq27(trafficSources.id, activeSource.id)).run();
|
|
29643
|
+
sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
|
|
29447
29644
|
} else {
|
|
29448
29645
|
const newId = crypto24.randomUUID();
|
|
29449
29646
|
app.db.insert(trafficSources).values({
|
|
@@ -29460,7 +29657,7 @@ async function trafficRoutes(app, opts) {
|
|
|
29460
29657
|
createdAt: now,
|
|
29461
29658
|
updatedAt: now
|
|
29462
29659
|
}).run();
|
|
29463
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
29660
|
+
sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, newId)).get();
|
|
29464
29661
|
}
|
|
29465
29662
|
writeAuditLog(app.db, {
|
|
29466
29663
|
projectId: project.id,
|
|
@@ -29514,7 +29711,7 @@ async function trafficRoutes(app, opts) {
|
|
|
29514
29711
|
createdAt: existing?.createdAt ?? now,
|
|
29515
29712
|
updatedAt: now
|
|
29516
29713
|
});
|
|
29517
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
29714
|
+
const activeSource = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
|
|
29518
29715
|
const config = { projectId, teamId, environment };
|
|
29519
29716
|
const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
|
|
29520
29717
|
const { sourceRow, scheduleCreated } = app.db.transaction((tx) => {
|
|
@@ -29526,8 +29723,8 @@ async function trafficRoutes(app, opts) {
|
|
|
29526
29723
|
lastError: null,
|
|
29527
29724
|
configJson: config,
|
|
29528
29725
|
updatedAt: now
|
|
29529
|
-
}).where(
|
|
29530
|
-
row = tx.select().from(trafficSources).where(
|
|
29726
|
+
}).where(eq27(trafficSources.id, activeSource.id)).run();
|
|
29727
|
+
row = tx.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
|
|
29531
29728
|
} else {
|
|
29532
29729
|
const newId = crypto24.randomUUID();
|
|
29533
29730
|
tx.insert(trafficSources).values({
|
|
@@ -29552,12 +29749,12 @@ async function trafficRoutes(app, opts) {
|
|
|
29552
29749
|
createdAt: now,
|
|
29553
29750
|
updatedAt: now
|
|
29554
29751
|
}).run();
|
|
29555
|
-
row = tx.select().from(trafficSources).where(
|
|
29752
|
+
row = tx.select().from(trafficSources).where(eq27(trafficSources.id, newId)).get();
|
|
29556
29753
|
}
|
|
29557
29754
|
const existingSchedule = tx.select().from(schedules).where(
|
|
29558
|
-
|
|
29559
|
-
|
|
29560
|
-
|
|
29755
|
+
and21(
|
|
29756
|
+
eq27(schedules.projectId, project.id),
|
|
29757
|
+
eq27(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
29561
29758
|
)
|
|
29562
29759
|
).get();
|
|
29563
29760
|
let created = false;
|
|
@@ -29606,7 +29803,7 @@ async function trafficRoutes(app, opts) {
|
|
|
29606
29803
|
});
|
|
29607
29804
|
app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
|
|
29608
29805
|
const project = resolveProject(app.db, request.params.name);
|
|
29609
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
29806
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, request.params.id)).get();
|
|
29610
29807
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
29611
29808
|
throw notFound("Traffic source", request.params.id);
|
|
29612
29809
|
}
|
|
@@ -29632,8 +29829,8 @@ async function trafficRoutes(app, opts) {
|
|
|
29632
29829
|
const markFailed = (msg, errorCode) => {
|
|
29633
29830
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
29634
29831
|
app.db.transaction((tx) => {
|
|
29635
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
29636
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
29832
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq27(runs.id, runId)).run();
|
|
29833
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq27(trafficSources.id, sourceRow.id)).run();
|
|
29637
29834
|
});
|
|
29638
29835
|
try {
|
|
29639
29836
|
opts.onTrafficSynced?.({
|
|
@@ -29714,7 +29911,7 @@ async function trafficRoutes(app, opts) {
|
|
|
29714
29911
|
}
|
|
29715
29912
|
const credential = credentialStore.getConnection(project.name);
|
|
29716
29913
|
if (!credential) {
|
|
29717
|
-
app.db.delete(runs).where(
|
|
29914
|
+
app.db.delete(runs).where(eq27(runs.id, runId)).run();
|
|
29718
29915
|
throw validationError(
|
|
29719
29916
|
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
29720
29917
|
);
|
|
@@ -29763,12 +29960,12 @@ async function trafficRoutes(app, opts) {
|
|
|
29763
29960
|
auditAction = "traffic.vercel.synced";
|
|
29764
29961
|
const credentialStore = opts.vercelTrafficCredentialStore;
|
|
29765
29962
|
if (!credentialStore) {
|
|
29766
|
-
app.db.delete(runs).where(
|
|
29963
|
+
app.db.delete(runs).where(eq27(runs.id, runId)).run();
|
|
29767
29964
|
throw validationError("Vercel traffic credential storage is not configured for this deployment");
|
|
29768
29965
|
}
|
|
29769
29966
|
const credential = credentialStore.getConnection(project.name);
|
|
29770
29967
|
if (!credential) {
|
|
29771
|
-
app.db.delete(runs).where(
|
|
29968
|
+
app.db.delete(runs).where(eq27(runs.id, runId)).run();
|
|
29772
29969
|
throw validationError(
|
|
29773
29970
|
`No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
|
|
29774
29971
|
);
|
|
@@ -29868,7 +30065,7 @@ async function trafficRoutes(app, opts) {
|
|
|
29868
30065
|
let aiReferralHitsCount = 0;
|
|
29869
30066
|
let unknownHitsCount = 0;
|
|
29870
30067
|
app.db.transaction((tx) => {
|
|
29871
|
-
const latestRow = tx.select().from(trafficSources).where(
|
|
30068
|
+
const latestRow = tx.select().from(trafficSources).where(eq27(trafficSources.id, sourceRow.id)).get();
|
|
29872
30069
|
const previousIds = latestRow.lastEventIds ?? [];
|
|
29873
30070
|
const seenEventIds = new Set(previousIds);
|
|
29874
30071
|
const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
|
|
@@ -30036,8 +30233,8 @@ async function trafficRoutes(app, opts) {
|
|
|
30036
30233
|
if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
|
|
30037
30234
|
sourceUpdate.lastCursor = nextCursor ?? null;
|
|
30038
30235
|
}
|
|
30039
|
-
tx.update(trafficSources).set(sourceUpdate).where(
|
|
30040
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
30236
|
+
tx.update(trafficSources).set(sourceUpdate).where(eq27(trafficSources.id, sourceRow.id)).run();
|
|
30237
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq27(runs.id, runId)).run();
|
|
30041
30238
|
});
|
|
30042
30239
|
writeAuditLog(app.db, {
|
|
30043
30240
|
projectId: project.id,
|
|
@@ -30089,7 +30286,7 @@ async function trafficRoutes(app, opts) {
|
|
|
30089
30286
|
});
|
|
30090
30287
|
app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
|
|
30091
30288
|
const project = resolveProject(app.db, request.params.name);
|
|
30092
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
30289
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, request.params.id)).get();
|
|
30093
30290
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
30094
30291
|
throw notFound("Traffic source", request.params.id);
|
|
30095
30292
|
}
|
|
@@ -30274,36 +30471,36 @@ async function trafficRoutes(app, opts) {
|
|
|
30274
30471
|
});
|
|
30275
30472
|
function buildSourceDetail(projectId, row, since) {
|
|
30276
30473
|
const crawlerTotals = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
30277
|
-
|
|
30278
|
-
|
|
30474
|
+
and21(
|
|
30475
|
+
eq27(crawlerEventsHourly.sourceId, row.id),
|
|
30279
30476
|
gte4(crawlerEventsHourly.tsHour, since)
|
|
30280
30477
|
)
|
|
30281
30478
|
).get();
|
|
30282
30479
|
const aiUserFetchTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
30283
|
-
|
|
30284
|
-
|
|
30480
|
+
and21(
|
|
30481
|
+
eq27(aiUserFetchEventsHourly.sourceId, row.id),
|
|
30285
30482
|
gte4(aiUserFetchEventsHourly.tsHour, since)
|
|
30286
30483
|
)
|
|
30287
30484
|
).get();
|
|
30288
30485
|
const aiTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
30289
|
-
|
|
30290
|
-
|
|
30486
|
+
and21(
|
|
30487
|
+
eq27(aiReferralEventsHourly.sourceId, row.id),
|
|
30291
30488
|
gte4(aiReferralEventsHourly.tsHour, since)
|
|
30292
30489
|
)
|
|
30293
30490
|
).get();
|
|
30294
30491
|
const sampleTotals = app.db.select({ total: sql11`COUNT(*)` }).from(rawEventSamples).where(
|
|
30295
|
-
|
|
30296
|
-
|
|
30492
|
+
and21(
|
|
30493
|
+
eq27(rawEventSamples.sourceId, row.id),
|
|
30297
30494
|
gte4(rawEventSamples.ts, since)
|
|
30298
30495
|
)
|
|
30299
30496
|
).get();
|
|
30300
30497
|
const latestRun = app.db.select().from(runs).where(
|
|
30301
|
-
|
|
30302
|
-
|
|
30303
|
-
|
|
30304
|
-
|
|
30498
|
+
and21(
|
|
30499
|
+
eq27(runs.projectId, projectId),
|
|
30500
|
+
eq27(runs.kind, RunKinds["traffic-sync"]),
|
|
30501
|
+
eq27(runs.sourceId, row.id)
|
|
30305
30502
|
)
|
|
30306
|
-
).orderBy(
|
|
30503
|
+
).orderBy(desc15(runs.startedAt)).limit(1).get();
|
|
30307
30504
|
return {
|
|
30308
30505
|
...rowToDto(row),
|
|
30309
30506
|
totals24h: {
|
|
@@ -30329,7 +30526,7 @@ async function trafficRoutes(app, opts) {
|
|
|
30329
30526
|
"`advanceToNow` must be `true`. There is no implicit reset."
|
|
30330
30527
|
);
|
|
30331
30528
|
}
|
|
30332
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
30529
|
+
const sourceRow = app.db.select().from(trafficSources).where(and21(eq27(trafficSources.projectId, project.id), eq27(trafficSources.id, request.params.id))).get();
|
|
30333
30530
|
if (!sourceRow) {
|
|
30334
30531
|
throw notFound("traffic source", request.params.id);
|
|
30335
30532
|
}
|
|
@@ -30346,7 +30543,7 @@ async function trafficRoutes(app, opts) {
|
|
|
30346
30543
|
status: TrafficSourceStatuses.connected,
|
|
30347
30544
|
lastError: null,
|
|
30348
30545
|
updatedAt: now
|
|
30349
|
-
}).where(
|
|
30546
|
+
}).where(eq27(trafficSources.id, sourceRow.id)).run();
|
|
30350
30547
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
30351
30548
|
projectId: project.id,
|
|
30352
30549
|
actor: "api",
|
|
@@ -30354,20 +30551,20 @@ async function trafficRoutes(app, opts) {
|
|
|
30354
30551
|
entityType: "traffic_source",
|
|
30355
30552
|
entityId: sourceRow.id
|
|
30356
30553
|
}));
|
|
30357
|
-
updatedRow = tx.select().from(trafficSources).where(
|
|
30554
|
+
updatedRow = tx.select().from(trafficSources).where(eq27(trafficSources.id, sourceRow.id)).get();
|
|
30358
30555
|
});
|
|
30359
30556
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
30360
30557
|
});
|
|
30361
30558
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
30362
30559
|
const project = resolveProject(app.db, request.params.name);
|
|
30363
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
30560
|
+
const rows = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
|
|
30364
30561
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
|
|
30365
30562
|
const response = { sources };
|
|
30366
30563
|
return response;
|
|
30367
30564
|
});
|
|
30368
30565
|
app.get("/projects/:name/traffic/status", async (request) => {
|
|
30369
30566
|
const project = resolveProject(app.db, request.params.name);
|
|
30370
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
30567
|
+
const rows = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
|
|
30371
30568
|
const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
|
|
30372
30569
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
|
|
30373
30570
|
const response = { sources };
|
|
@@ -30377,7 +30574,7 @@ async function trafficRoutes(app, opts) {
|
|
|
30377
30574
|
"/projects/:name/traffic/sources/:id",
|
|
30378
30575
|
async (request) => {
|
|
30379
30576
|
const project = resolveProject(app.db, request.params.name);
|
|
30380
|
-
const row = app.db.select().from(trafficSources).where(
|
|
30577
|
+
const row = app.db.select().from(trafficSources).where(eq27(trafficSources.id, request.params.id)).get();
|
|
30381
30578
|
if (!row || row.projectId !== project.id) {
|
|
30382
30579
|
throw notFound("Traffic source", request.params.id);
|
|
30383
30580
|
}
|
|
@@ -30428,15 +30625,15 @@ async function trafficRoutes(app, opts) {
|
|
|
30428
30625
|
let aiReferralTotal = 0;
|
|
30429
30626
|
if (kind === "all" || kind === TrafficEventKinds.crawler) {
|
|
30430
30627
|
const crawlerFilters = [
|
|
30431
|
-
|
|
30628
|
+
eq27(crawlerEventsHourly.projectId, project.id),
|
|
30432
30629
|
gte4(crawlerEventsHourly.tsHour, sinceIso),
|
|
30433
30630
|
lte3(crawlerEventsHourly.tsHour, untilIso)
|
|
30434
30631
|
];
|
|
30435
|
-
if (sourceIdParam) crawlerFilters.push(
|
|
30436
|
-
const crawlerWhere =
|
|
30632
|
+
if (sourceIdParam) crawlerFilters.push(eq27(crawlerEventsHourly.sourceId, sourceIdParam));
|
|
30633
|
+
const crawlerWhere = and21(...crawlerFilters);
|
|
30437
30634
|
const total = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
|
|
30438
30635
|
crawlerTotal = Number(total?.total ?? 0);
|
|
30439
|
-
const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(
|
|
30636
|
+
const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc15(crawlerEventsHourly.tsHour)).limit(limit).all();
|
|
30440
30637
|
for (const r of rows) {
|
|
30441
30638
|
events.push({
|
|
30442
30639
|
kind: TrafficEventKinds.crawler,
|
|
@@ -30453,15 +30650,15 @@ async function trafficRoutes(app, opts) {
|
|
|
30453
30650
|
}
|
|
30454
30651
|
if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
|
|
30455
30652
|
const userFetchFilters = [
|
|
30456
|
-
|
|
30653
|
+
eq27(aiUserFetchEventsHourly.projectId, project.id),
|
|
30457
30654
|
gte4(aiUserFetchEventsHourly.tsHour, sinceIso),
|
|
30458
30655
|
lte3(aiUserFetchEventsHourly.tsHour, untilIso)
|
|
30459
30656
|
];
|
|
30460
|
-
if (sourceIdParam) userFetchFilters.push(
|
|
30461
|
-
const userFetchWhere =
|
|
30657
|
+
if (sourceIdParam) userFetchFilters.push(eq27(aiUserFetchEventsHourly.sourceId, sourceIdParam));
|
|
30658
|
+
const userFetchWhere = and21(...userFetchFilters);
|
|
30462
30659
|
const total = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
|
|
30463
30660
|
aiUserFetchTotal = Number(total?.total ?? 0);
|
|
30464
|
-
const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(
|
|
30661
|
+
const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc15(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
|
|
30465
30662
|
for (const r of rows) {
|
|
30466
30663
|
events.push({
|
|
30467
30664
|
kind: TrafficEventKinds["ai-user-fetch"],
|
|
@@ -30478,15 +30675,15 @@ async function trafficRoutes(app, opts) {
|
|
|
30478
30675
|
}
|
|
30479
30676
|
if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
|
|
30480
30677
|
const aiFilters = [
|
|
30481
|
-
|
|
30678
|
+
eq27(aiReferralEventsHourly.projectId, project.id),
|
|
30482
30679
|
gte4(aiReferralEventsHourly.tsHour, sinceIso),
|
|
30483
30680
|
lte3(aiReferralEventsHourly.tsHour, untilIso)
|
|
30484
30681
|
];
|
|
30485
|
-
if (sourceIdParam) aiFilters.push(
|
|
30486
|
-
const aiWhere =
|
|
30682
|
+
if (sourceIdParam) aiFilters.push(eq27(aiReferralEventsHourly.sourceId, sourceIdParam));
|
|
30683
|
+
const aiWhere = and21(...aiFilters);
|
|
30487
30684
|
const total = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
|
|
30488
30685
|
aiReferralTotal = Number(total?.total ?? 0);
|
|
30489
|
-
const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(
|
|
30686
|
+
const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc15(aiReferralEventsHourly.tsHour)).limit(limit).all();
|
|
30490
30687
|
for (const r of rows) {
|
|
30491
30688
|
events.push({
|
|
30492
30689
|
kind: TrafficEventKinds["ai-referral"],
|
|
@@ -30687,7 +30884,7 @@ function readInstalledManifest(skillDir) {
|
|
|
30687
30884
|
var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck];
|
|
30688
30885
|
|
|
30689
30886
|
// ../api-routes/src/doctor/checks/backlinks.ts
|
|
30690
|
-
import { and as
|
|
30887
|
+
import { and as and22, eq as eq28 } from "drizzle-orm";
|
|
30691
30888
|
function skippedNoProject() {
|
|
30692
30889
|
return {
|
|
30693
30890
|
status: CheckStatuses.skipped,
|
|
@@ -30703,8 +30900,8 @@ var BACKLINKS_CHECKS = [
|
|
|
30703
30900
|
title: "Backlinks source connected",
|
|
30704
30901
|
run: (ctx) => {
|
|
30705
30902
|
if (!ctx.project) return skippedNoProject();
|
|
30706
|
-
const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(
|
|
30707
|
-
const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(
|
|
30903
|
+
const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq28(projects.id, ctx.project.id)).get();
|
|
30904
|
+
const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
|
|
30708
30905
|
const ccConnected = projectRow?.autoExtract === true && !!readySync;
|
|
30709
30906
|
const bingConnected = !!ctx.bingConnectionStore?.getConnection(ctx.project.canonicalDomain);
|
|
30710
30907
|
const connected = [];
|
|
@@ -30719,9 +30916,9 @@ var BACKLINKS_CHECKS = [
|
|
|
30719
30916
|
details: { commoncrawl: ccConnected, bingWebmaster: bingConnected }
|
|
30720
30917
|
};
|
|
30721
30918
|
}
|
|
30722
|
-
const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(
|
|
30723
|
-
|
|
30724
|
-
|
|
30919
|
+
const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and22(
|
|
30920
|
+
eq28(backlinkSummaries.projectId, ctx.project.id),
|
|
30921
|
+
eq28(backlinkSummaries.source, BacklinkSources.commoncrawl)
|
|
30725
30922
|
)).limit(1).get() : false;
|
|
30726
30923
|
return {
|
|
30727
30924
|
status: CheckStatuses.ok,
|
|
@@ -30881,7 +31078,7 @@ var BING_AUTH_CHECKS = [
|
|
|
30881
31078
|
];
|
|
30882
31079
|
|
|
30883
31080
|
// ../api-routes/src/doctor/checks/content.ts
|
|
30884
|
-
import { eq as
|
|
31081
|
+
import { eq as eq29 } from "drizzle-orm";
|
|
30885
31082
|
var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
|
|
30886
31083
|
var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
|
|
30887
31084
|
function skippedNoProject2() {
|
|
@@ -30894,7 +31091,7 @@ function skippedNoProject2() {
|
|
|
30894
31091
|
}
|
|
30895
31092
|
function loadProject(ctx) {
|
|
30896
31093
|
if (!ctx.project) return null;
|
|
30897
|
-
return ctx.db.select().from(projects).where(
|
|
31094
|
+
return ctx.db.select().from(projects).where(eq29(projects.id, ctx.project.id)).get() ?? null;
|
|
30898
31095
|
}
|
|
30899
31096
|
function percent(value) {
|
|
30900
31097
|
return Math.round(value * 100);
|
|
@@ -30986,7 +31183,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
|
|
|
30986
31183
|
);
|
|
30987
31184
|
|
|
30988
31185
|
// ../api-routes/src/doctor/checks/ads.ts
|
|
30989
|
-
import { eq as
|
|
31186
|
+
import { eq as eq30 } from "drizzle-orm";
|
|
30990
31187
|
var RECENT_SYNC_WARN_DAYS = 7;
|
|
30991
31188
|
var RECENT_SYNC_FAIL_DAYS = 30;
|
|
30992
31189
|
var adsConnectionCheck = {
|
|
@@ -31003,7 +31200,7 @@ var adsConnectionCheck = {
|
|
|
31003
31200
|
remediation: null
|
|
31004
31201
|
};
|
|
31005
31202
|
}
|
|
31006
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
31203
|
+
const row = ctx.db.select().from(adsConnections).where(eq30(adsConnections.projectId, ctx.project.id)).get();
|
|
31007
31204
|
if (!row) {
|
|
31008
31205
|
return {
|
|
31009
31206
|
status: CheckStatuses.skipped,
|
|
@@ -31053,7 +31250,7 @@ var adsRecentSyncCheck = {
|
|
|
31053
31250
|
remediation: null
|
|
31054
31251
|
};
|
|
31055
31252
|
}
|
|
31056
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
31253
|
+
const row = ctx.db.select().from(adsConnections).where(eq30(adsConnections.projectId, ctx.project.id)).get();
|
|
31057
31254
|
if (!row) {
|
|
31058
31255
|
return {
|
|
31059
31256
|
status: CheckStatuses.skipped,
|
|
@@ -31244,7 +31441,7 @@ var ga4ConnectionCheck = {
|
|
|
31244
31441
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
31245
31442
|
|
|
31246
31443
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
31247
|
-
import { and as
|
|
31444
|
+
import { and as and23, eq as eq31 } from "drizzle-orm";
|
|
31248
31445
|
var RECENT_SYNC_WARN_DAYS2 = 7;
|
|
31249
31446
|
var RECENT_SYNC_FAIL_DAYS2 = 30;
|
|
31250
31447
|
function skippedNoProject3() {
|
|
@@ -31477,7 +31674,7 @@ var recentSyncCheck = {
|
|
|
31477
31674
|
title: "GBP recent sync",
|
|
31478
31675
|
run: (ctx) => {
|
|
31479
31676
|
if (!ctx.project) return skippedNoProject3();
|
|
31480
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
31677
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and23(eq31(gbpLocations.projectId, ctx.project.id), eq31(gbpLocations.selected, true))).all();
|
|
31481
31678
|
if (selected.length === 0) {
|
|
31482
31679
|
return {
|
|
31483
31680
|
status: CheckStatuses.skipped,
|
|
@@ -31537,7 +31734,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
31537
31734
|
);
|
|
31538
31735
|
|
|
31539
31736
|
// ../api-routes/src/doctor/checks/places.ts
|
|
31540
|
-
import { eq as
|
|
31737
|
+
import { eq as eq32 } from "drizzle-orm";
|
|
31541
31738
|
var apiKeyCheck = {
|
|
31542
31739
|
id: "gbp.places.api-key",
|
|
31543
31740
|
category: CheckCategories.auth,
|
|
@@ -31582,7 +31779,7 @@ var apiKeyCheck = {
|
|
|
31582
31779
|
details: { tier: cfg.tier }
|
|
31583
31780
|
};
|
|
31584
31781
|
}
|
|
31585
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
31782
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq32(gbpLocations.projectId, ctx.project.id)).all();
|
|
31586
31783
|
const selected = rows.filter((r) => r.selected);
|
|
31587
31784
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
31588
31785
|
const details = {
|
|
@@ -32033,7 +32230,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
32033
32230
|
];
|
|
32034
32231
|
|
|
32035
32232
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
32036
|
-
import { and as
|
|
32233
|
+
import { and as and24, eq as eq33, gte as gte5, ne as ne4, sql as sql12 } from "drizzle-orm";
|
|
32037
32234
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
32038
32235
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
32039
32236
|
function skippedNoProject5() {
|
|
@@ -32047,8 +32244,8 @@ function skippedNoProject5() {
|
|
|
32047
32244
|
function loadProbes(ctx) {
|
|
32048
32245
|
if (!ctx.project) return [];
|
|
32049
32246
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
32050
|
-
|
|
32051
|
-
|
|
32247
|
+
and24(
|
|
32248
|
+
eq33(trafficSources.projectId, ctx.project.id),
|
|
32052
32249
|
ne4(trafficSources.status, TrafficSourceStatuses.archived)
|
|
32053
32250
|
)
|
|
32054
32251
|
).all();
|
|
@@ -32128,16 +32325,16 @@ var recentDataCheck = {
|
|
|
32128
32325
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
32129
32326
|
const recentCrawlers = Number(
|
|
32130
32327
|
ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
32131
|
-
|
|
32132
|
-
|
|
32328
|
+
and24(
|
|
32329
|
+
eq33(crawlerEventsHourly.projectId, ctx.project.id),
|
|
32133
32330
|
gte5(crawlerEventsHourly.tsHour, warnCutoff)
|
|
32134
32331
|
)
|
|
32135
32332
|
).get()?.total ?? 0
|
|
32136
32333
|
);
|
|
32137
32334
|
const recentReferrals = Number(
|
|
32138
32335
|
ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
32139
|
-
|
|
32140
|
-
|
|
32336
|
+
and24(
|
|
32337
|
+
eq33(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
32141
32338
|
gte5(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
32142
32339
|
)
|
|
32143
32340
|
).get()?.total ?? 0
|
|
@@ -32152,16 +32349,16 @@ var recentDataCheck = {
|
|
|
32152
32349
|
}
|
|
32153
32350
|
const olderCrawlers = Number(
|
|
32154
32351
|
ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
32155
|
-
|
|
32156
|
-
|
|
32352
|
+
and24(
|
|
32353
|
+
eq33(crawlerEventsHourly.projectId, ctx.project.id),
|
|
32157
32354
|
gte5(crawlerEventsHourly.tsHour, failCutoff)
|
|
32158
32355
|
)
|
|
32159
32356
|
).get()?.total ?? 0
|
|
32160
32357
|
);
|
|
32161
32358
|
const olderReferrals = Number(
|
|
32162
32359
|
ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
32163
|
-
|
|
32164
|
-
|
|
32360
|
+
and24(
|
|
32361
|
+
eq33(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
32165
32362
|
gte5(aiReferralEventsHourly.tsHour, failCutoff)
|
|
32166
32363
|
)
|
|
32167
32364
|
).get()?.total ?? 0
|
|
@@ -32575,7 +32772,7 @@ async function doctorRoutes(app, opts) {
|
|
|
32575
32772
|
|
|
32576
32773
|
// ../api-routes/src/discovery/routes.ts
|
|
32577
32774
|
import crypto26 from "crypto";
|
|
32578
|
-
import { and as
|
|
32775
|
+
import { and as and25, desc as desc16, eq as eq34, gte as gte6, inArray as inArray12 } from "drizzle-orm";
|
|
32579
32776
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
32580
32777
|
async function discoveryRoutes(app, opts) {
|
|
32581
32778
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -32607,16 +32804,16 @@ async function discoveryRoutes(app, opts) {
|
|
|
32607
32804
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
32608
32805
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
32609
32806
|
const decision = app.db.transaction((tx) => {
|
|
32610
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
32611
|
-
|
|
32612
|
-
|
|
32613
|
-
|
|
32807
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and25(
|
|
32808
|
+
eq34(discoverySessions.projectId, project.id),
|
|
32809
|
+
eq34(discoverySessions.icpDescription, icpDescription),
|
|
32810
|
+
inArray12(discoverySessions.status, [
|
|
32614
32811
|
DiscoverySessionStatuses.queued,
|
|
32615
32812
|
DiscoverySessionStatuses.seeding,
|
|
32616
32813
|
DiscoverySessionStatuses.probing
|
|
32617
32814
|
]),
|
|
32618
32815
|
gte6(discoverySessions.createdAt, ageFloorIso)
|
|
32619
|
-
)).orderBy(
|
|
32816
|
+
)).orderBy(desc16(discoverySessions.createdAt)).get();
|
|
32620
32817
|
if (existing && existing.runId) {
|
|
32621
32818
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
32622
32819
|
}
|
|
@@ -32679,7 +32876,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
32679
32876
|
const project = resolveProject(app.db, request.params.name);
|
|
32680
32877
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
32681
32878
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
32682
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
32879
|
+
const rows = app.db.select().from(discoverySessions).where(eq34(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
|
|
32683
32880
|
return reply.send(rows.map(serializeSession));
|
|
32684
32881
|
}
|
|
32685
32882
|
);
|
|
@@ -32687,11 +32884,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
32687
32884
|
"/projects/:name/discover/sessions/:id",
|
|
32688
32885
|
async (request, reply) => {
|
|
32689
32886
|
const project = resolveProject(app.db, request.params.name);
|
|
32690
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
32887
|
+
const session = app.db.select().from(discoverySessions).where(eq34(discoverySessions.id, request.params.id)).get();
|
|
32691
32888
|
if (!session || session.projectId !== project.id) {
|
|
32692
32889
|
throw notFound("Discovery session", request.params.id);
|
|
32693
32890
|
}
|
|
32694
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
32891
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq34(discoveryProbes.sessionId, session.id)).all();
|
|
32695
32892
|
const detail = {
|
|
32696
32893
|
...serializeSession(session),
|
|
32697
32894
|
probes: probeRows.map(serializeProbe)
|
|
@@ -32703,12 +32900,12 @@ async function discoveryRoutes(app, opts) {
|
|
|
32703
32900
|
"/projects/:name/discover/sessions/:id/promote",
|
|
32704
32901
|
async (request, reply) => {
|
|
32705
32902
|
const project = resolveProject(app.db, request.params.name);
|
|
32706
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
32903
|
+
const session = app.db.select().from(discoverySessions).where(eq34(discoverySessions.id, request.params.id)).get();
|
|
32707
32904
|
if (!session || session.projectId !== project.id) {
|
|
32708
32905
|
throw notFound("Discovery session", request.params.id);
|
|
32709
32906
|
}
|
|
32710
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
32711
|
-
const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
32907
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq34(discoveryProbes.sessionId, session.id)).all();
|
|
32908
|
+
const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq34(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
|
|
32712
32909
|
const seenCompetitors = new Set(existingCompetitors);
|
|
32713
32910
|
const cited = /* @__PURE__ */ new Set();
|
|
32714
32911
|
const aspirational = /* @__PURE__ */ new Set();
|
|
@@ -32737,7 +32934,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
32737
32934
|
);
|
|
32738
32935
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
32739
32936
|
const project = resolveProject(app.db, request.params.name);
|
|
32740
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
32937
|
+
const session = app.db.select().from(discoverySessions).where(eq34(discoverySessions.id, request.params.id)).get();
|
|
32741
32938
|
if (!session || session.projectId !== project.id) {
|
|
32742
32939
|
throw notFound("Discovery session", request.params.id);
|
|
32743
32940
|
}
|
|
@@ -32760,7 +32957,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
32760
32957
|
const bucketSet = new Set(buckets);
|
|
32761
32958
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
32762
32959
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
32763
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
32960
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq34(discoveryProbes.sessionId, session.id)).all();
|
|
32764
32961
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
32765
32962
|
for (const probe of probeRows) {
|
|
32766
32963
|
if (!probe.bucket) continue;
|
|
@@ -32768,7 +32965,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
32768
32965
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
32769
32966
|
}
|
|
32770
32967
|
const existingQueries = new Set(
|
|
32771
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
32968
|
+
app.db.select({ query: queries.query }).from(queries).where(eq34(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
32772
32969
|
);
|
|
32773
32970
|
const promotedQueries = [];
|
|
32774
32971
|
const skippedQueries = [];
|
|
@@ -32784,7 +32981,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
32784
32981
|
const skippedCompetitors = [];
|
|
32785
32982
|
if (includeCompetitors) {
|
|
32786
32983
|
const existingCompetitors = new Set(
|
|
32787
|
-
app.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
32984
|
+
app.db.select({ domain: competitors.domain }).from(competitors).where(eq34(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
|
|
32788
32985
|
);
|
|
32789
32986
|
const competitorMap = parseCompetitorMap(session.competitorMap);
|
|
32790
32987
|
for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
|
|
@@ -32891,7 +33088,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
|
|
|
32891
33088
|
|
|
32892
33089
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
32893
33090
|
import crypto27 from "crypto";
|
|
32894
|
-
import { eq as
|
|
33091
|
+
import { eq as eq35 } from "drizzle-orm";
|
|
32895
33092
|
var DEFAULT_MAX_PROBES = 100;
|
|
32896
33093
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
32897
33094
|
function classifyProbeBucket(input) {
|
|
@@ -32904,7 +33101,7 @@ function classifyProbeBucket(input) {
|
|
|
32904
33101
|
}
|
|
32905
33102
|
function buildCompetitorMap(probes, project, classification = {}) {
|
|
32906
33103
|
const canonical = new Set(project.canonicalDomains.map((d) => d.toLowerCase()));
|
|
32907
|
-
const
|
|
33104
|
+
const counts2 = /* @__PURE__ */ new Map();
|
|
32908
33105
|
for (const probe of probes) {
|
|
32909
33106
|
const seenInProbe = /* @__PURE__ */ new Set();
|
|
32910
33107
|
for (const raw of probe.citedDomains) {
|
|
@@ -32912,10 +33109,10 @@ function buildCompetitorMap(probes, project, classification = {}) {
|
|
|
32912
33109
|
if (canonical.has(domain)) continue;
|
|
32913
33110
|
if (seenInProbe.has(domain)) continue;
|
|
32914
33111
|
seenInProbe.add(domain);
|
|
32915
|
-
|
|
33112
|
+
counts2.set(domain, (counts2.get(domain) ?? 0) + 1);
|
|
32916
33113
|
}
|
|
32917
33114
|
}
|
|
32918
|
-
return Array.from(
|
|
33115
|
+
return Array.from(counts2.entries()).map(([domain, hits]) => ({
|
|
32919
33116
|
domain,
|
|
32920
33117
|
hits,
|
|
32921
33118
|
competitorType: classification[domain] ?? DiscoveryCompetitorTypes.unknown
|
|
@@ -32945,7 +33142,7 @@ async function executeDiscovery(opts) {
|
|
|
32945
33142
|
status: DiscoverySessionStatuses.seeding,
|
|
32946
33143
|
dedupThreshold,
|
|
32947
33144
|
startedAt
|
|
32948
|
-
}).where(
|
|
33145
|
+
}).where(eq35(discoverySessions.id, opts.sessionId)).run();
|
|
32949
33146
|
const seedResult = await opts.deps.seed({
|
|
32950
33147
|
project: opts.project,
|
|
32951
33148
|
icpDescription: opts.icpDescription,
|
|
@@ -32971,7 +33168,7 @@ async function executeDiscovery(opts) {
|
|
|
32971
33168
|
seedCountRaw,
|
|
32972
33169
|
seedCount,
|
|
32973
33170
|
warning
|
|
32974
|
-
}).where(
|
|
33171
|
+
}).where(eq35(discoverySessions.id, opts.sessionId)).run();
|
|
32975
33172
|
const probeRows = [];
|
|
32976
33173
|
const buckets = { cited: 0, aspirational: 0, "wasted-surface": 0 };
|
|
32977
33174
|
for (const query of probedCanonicals) {
|
|
@@ -33012,7 +33209,7 @@ async function executeDiscovery(opts) {
|
|
|
33012
33209
|
wastedCount: buckets["wasted-surface"],
|
|
33013
33210
|
competitorMap,
|
|
33014
33211
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
33015
|
-
}).where(
|
|
33212
|
+
}).where(eq35(discoverySessions.id, opts.sessionId)).run();
|
|
33016
33213
|
upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
|
|
33017
33214
|
return {
|
|
33018
33215
|
buckets,
|
|
@@ -33052,7 +33249,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
33052
33249
|
status: DiscoverySessionStatuses.failed,
|
|
33053
33250
|
error,
|
|
33054
33251
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
33055
|
-
}).where(
|
|
33252
|
+
}).where(eq35(discoverySessions.id, sessionId)).run();
|
|
33056
33253
|
}
|
|
33057
33254
|
function dedupeStrings(input) {
|
|
33058
33255
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -33070,7 +33267,7 @@ function dedupeStrings(input) {
|
|
|
33070
33267
|
|
|
33071
33268
|
// ../api-routes/src/technical-aeo.ts
|
|
33072
33269
|
import crypto28 from "crypto";
|
|
33073
|
-
import { and as
|
|
33270
|
+
import { and as and26, asc as asc4, count, desc as desc17, eq as eq36, inArray as inArray13 } from "drizzle-orm";
|
|
33074
33271
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
33075
33272
|
function emptyScore(projectName) {
|
|
33076
33273
|
return {
|
|
@@ -33102,12 +33299,12 @@ function parsePositiveInt(value, fallback, max) {
|
|
|
33102
33299
|
async function technicalAeoRoutes(app, opts) {
|
|
33103
33300
|
app.get("/projects/:name/technical-aeo", async (request) => {
|
|
33104
33301
|
const project = resolveProject(app.db, request.params.name);
|
|
33105
|
-
const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs,
|
|
33106
|
-
|
|
33107
|
-
|
|
33108
|
-
|
|
33302
|
+
const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq36(siteAuditSnapshots.runId, runs.id)).where(and26(
|
|
33303
|
+
eq36(siteAuditSnapshots.projectId, project.id),
|
|
33304
|
+
eq36(runs.kind, RunKinds["site-audit"]),
|
|
33305
|
+
inArray13(runs.status, SURFACEABLE_STATUSES),
|
|
33109
33306
|
notProbeRun()
|
|
33110
|
-
)).orderBy(
|
|
33307
|
+
)).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(2).all();
|
|
33111
33308
|
const latest = rows[0];
|
|
33112
33309
|
if (!latest) return emptyScore(project.name);
|
|
33113
33310
|
const snap = latest.snap;
|
|
@@ -33137,24 +33334,24 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
33137
33334
|
});
|
|
33138
33335
|
app.get("/projects/:name/technical-aeo/pages", async (request) => {
|
|
33139
33336
|
const project = resolveProject(app.db, request.params.name);
|
|
33140
|
-
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs,
|
|
33141
|
-
|
|
33142
|
-
|
|
33143
|
-
|
|
33337
|
+
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq36(siteAuditSnapshots.runId, runs.id)).where(and26(
|
|
33338
|
+
eq36(siteAuditSnapshots.projectId, project.id),
|
|
33339
|
+
eq36(runs.kind, RunKinds["site-audit"]),
|
|
33340
|
+
inArray13(runs.status, SURFACEABLE_STATUSES),
|
|
33144
33341
|
notProbeRun()
|
|
33145
|
-
)).orderBy(
|
|
33342
|
+
)).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get();
|
|
33146
33343
|
if (!latest) {
|
|
33147
33344
|
return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
|
|
33148
33345
|
}
|
|
33149
33346
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
33150
|
-
const conds = [
|
|
33151
|
-
if (statusFilter) conds.push(
|
|
33152
|
-
const where =
|
|
33347
|
+
const conds = [eq36(siteAuditPages.runId, latest.runId)];
|
|
33348
|
+
if (statusFilter) conds.push(eq36(siteAuditPages.status, statusFilter));
|
|
33349
|
+
const where = and26(...conds);
|
|
33153
33350
|
const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
|
|
33154
33351
|
const total = totalRow?.value ?? 0;
|
|
33155
33352
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
33156
33353
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
33157
|
-
const orderBy = request.query.sort === "score-desc" ?
|
|
33354
|
+
const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ? asc4(siteAuditPages.url) : asc4(siteAuditPages.overallScore);
|
|
33158
33355
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
33159
33356
|
const pages = rows.map((row) => ({
|
|
33160
33357
|
url: row.url,
|
|
@@ -33173,12 +33370,12 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
33173
33370
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
33174
33371
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
33175
33372
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
33176
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
33177
|
-
|
|
33178
|
-
|
|
33179
|
-
|
|
33373
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq36(siteAuditSnapshots.runId, runs.id)).where(and26(
|
|
33374
|
+
eq36(siteAuditSnapshots.projectId, project.id),
|
|
33375
|
+
eq36(runs.kind, RunKinds["site-audit"]),
|
|
33376
|
+
inArray13(runs.status, SURFACEABLE_STATUSES),
|
|
33180
33377
|
notProbeRun()
|
|
33181
|
-
)).orderBy(
|
|
33378
|
+
)).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
33182
33379
|
return { project: project.name, points: rows.reverse() };
|
|
33183
33380
|
});
|
|
33184
33381
|
app.post("/projects/:name/technical-aeo/runs", async (request) => {
|
|
@@ -33187,10 +33384,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
33187
33384
|
if (!parsed.success) {
|
|
33188
33385
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
|
|
33189
33386
|
}
|
|
33190
|
-
const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(
|
|
33191
|
-
|
|
33192
|
-
|
|
33193
|
-
|
|
33387
|
+
const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and26(
|
|
33388
|
+
eq36(runs.projectId, project.id),
|
|
33389
|
+
eq36(runs.kind, RunKinds["site-audit"]),
|
|
33390
|
+
inArray13(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
33194
33391
|
)).get();
|
|
33195
33392
|
if (existing) {
|
|
33196
33393
|
return { runId: existing.id, status: existing.status };
|
|
@@ -33293,6 +33490,7 @@ async function apiRoutes(app, opts) {
|
|
|
33293
33490
|
await api.register(intelligenceRoutes);
|
|
33294
33491
|
await api.register(reportRoutes);
|
|
33295
33492
|
await api.register(citationRoutes);
|
|
33493
|
+
await api.register(visibilityStatsRoutes);
|
|
33296
33494
|
await api.register(compositeRoutes);
|
|
33297
33495
|
await api.register(contentRoutes, {
|
|
33298
33496
|
explainContentRecommendation: opts.explainContentRecommendation,
|
|
@@ -33776,16 +33974,16 @@ var IntelligenceService = class {
|
|
|
33776
33974
|
*/
|
|
33777
33975
|
analyzeAndPersist(runId, projectId) {
|
|
33778
33976
|
const recentRuns = this.db.select().from(runs).where(
|
|
33779
|
-
|
|
33780
|
-
|
|
33781
|
-
or5(
|
|
33977
|
+
and27(
|
|
33978
|
+
eq37(runs.projectId, projectId),
|
|
33979
|
+
or5(eq37(runs.status, "completed"), eq37(runs.status, "partial")),
|
|
33782
33980
|
// Defensive: RunCoordinator already skips probes before this is
|
|
33783
33981
|
// called, but if a future call site invokes analyzeAndPersist
|
|
33784
33982
|
// directly for a probe, probes still must not pollute the
|
|
33785
33983
|
// intelligence window.
|
|
33786
33984
|
ne5(runs.trigger, RunTriggers.probe)
|
|
33787
33985
|
)
|
|
33788
|
-
).orderBy(
|
|
33986
|
+
).orderBy(desc18(runs.finishedAt), desc18(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
|
|
33789
33987
|
if (recentRuns.length === 0) {
|
|
33790
33988
|
log.info("intelligence.skip", { runId, reason: "no completed runs" });
|
|
33791
33989
|
return null;
|
|
@@ -33860,7 +34058,7 @@ var IntelligenceService = class {
|
|
|
33860
34058
|
* Returns the persisted insights so the coordinator can count critical/high.
|
|
33861
34059
|
*/
|
|
33862
34060
|
analyzeAndPersistGbp(runId, projectId) {
|
|
33863
|
-
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
34061
|
+
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq37(runs.id, runId)).get();
|
|
33864
34062
|
if (!runRow) {
|
|
33865
34063
|
log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
|
|
33866
34064
|
this.persistGbpInsights(runId, projectId, [], []);
|
|
@@ -33868,9 +34066,9 @@ var IntelligenceService = class {
|
|
|
33868
34066
|
}
|
|
33869
34067
|
const windowStart = runRow.startedAt ?? runRow.createdAt;
|
|
33870
34068
|
const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
33871
|
-
const selected = this.db.select().from(gbpLocations).where(
|
|
33872
|
-
|
|
33873
|
-
|
|
34069
|
+
const selected = this.db.select().from(gbpLocations).where(and27(
|
|
34070
|
+
eq37(gbpLocations.projectId, projectId),
|
|
34071
|
+
eq37(gbpLocations.selected, true),
|
|
33874
34072
|
gte7(gbpLocations.syncedAt, windowStart),
|
|
33875
34073
|
lte4(gbpLocations.syncedAt, windowEnd)
|
|
33876
34074
|
)).all();
|
|
@@ -33905,10 +34103,10 @@ var IntelligenceService = class {
|
|
|
33905
34103
|
}
|
|
33906
34104
|
/** Build the per-location signal bundle the GBP analyzer consumes. */
|
|
33907
34105
|
buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
|
|
33908
|
-
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(
|
|
33909
|
-
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(
|
|
33910
|
-
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(
|
|
33911
|
-
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(
|
|
34106
|
+
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and27(eq37(gbpDailyMetrics.projectId, projectId), eq37(gbpDailyMetrics.locationName, locationName))).all();
|
|
34107
|
+
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and27(eq37(gbpPlaceActions.projectId, projectId), eq37(gbpPlaceActions.locationName, locationName))).all();
|
|
34108
|
+
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and27(eq37(gbpLodgingSnapshots.projectId, projectId), eq37(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc18(gbpLodgingSnapshots.syncedAt)).limit(1).get();
|
|
34109
|
+
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and27(eq37(gbpPlaceDetails.projectId, projectId), eq37(gbpPlaceDetails.locationName, locationName))).orderBy(desc18(gbpPlaceDetails.syncedAt)).limit(1).get();
|
|
33912
34110
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
33913
34111
|
const summary = buildGbpSummary({
|
|
33914
34112
|
locationName,
|
|
@@ -33940,7 +34138,7 @@ var IntelligenceService = class {
|
|
|
33940
34138
|
/** Build the month-over-month keyword series for a location from the
|
|
33941
34139
|
* accumulating gbp_keyword_monthly table (latest complete month vs prior). */
|
|
33942
34140
|
buildGbpKeywordTrend(projectId, locationName) {
|
|
33943
|
-
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(
|
|
34141
|
+
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and27(eq37(gbpKeywordMonthly.projectId, projectId), eq37(gbpKeywordMonthly.locationName, locationName))).all();
|
|
33944
34142
|
if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
|
|
33945
34143
|
const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
|
|
33946
34144
|
const recentMonth = months[0] ?? null;
|
|
@@ -33971,7 +34169,7 @@ var IntelligenceService = class {
|
|
|
33971
34169
|
*/
|
|
33972
34170
|
persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
|
|
33973
34171
|
const covered = new Set(coveredLocationNames);
|
|
33974
|
-
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(
|
|
34172
|
+
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and27(eq37(insights.projectId, projectId), eq37(insights.provider, GBP_INSIGHT_PROVIDER))).all();
|
|
33975
34173
|
const staleIds = [];
|
|
33976
34174
|
const dismissedSlots = /* @__PURE__ */ new Set();
|
|
33977
34175
|
for (const row of existing) {
|
|
@@ -33982,7 +34180,7 @@ var IntelligenceService = class {
|
|
|
33982
34180
|
}
|
|
33983
34181
|
this.db.transaction((tx) => {
|
|
33984
34182
|
for (const id of staleIds) {
|
|
33985
|
-
tx.delete(insights).where(
|
|
34183
|
+
tx.delete(insights).where(eq37(insights.id, id)).run();
|
|
33986
34184
|
}
|
|
33987
34185
|
for (const insight of gbpInsights) {
|
|
33988
34186
|
const parsed = parseGbpInsightId(insight.id);
|
|
@@ -34060,7 +34258,7 @@ var IntelligenceService = class {
|
|
|
34060
34258
|
* create per run + aggregate). DB is left untouched.
|
|
34061
34259
|
*/
|
|
34062
34260
|
backfill(projectName, opts, onProgress) {
|
|
34063
|
-
const project = this.db.select().from(projects).where(
|
|
34261
|
+
const project = this.db.select().from(projects).where(eq37(projects.name, projectName)).get();
|
|
34064
34262
|
if (!project) {
|
|
34065
34263
|
throw new Error(`Project "${projectName}" not found`);
|
|
34066
34264
|
}
|
|
@@ -34073,9 +34271,9 @@ var IntelligenceService = class {
|
|
|
34073
34271
|
sinceTimestamp = parsed;
|
|
34074
34272
|
}
|
|
34075
34273
|
const allRuns = this.db.select().from(runs).where(
|
|
34076
|
-
|
|
34077
|
-
|
|
34078
|
-
or5(
|
|
34274
|
+
and27(
|
|
34275
|
+
eq37(runs.projectId, project.id),
|
|
34276
|
+
or5(eq37(runs.status, "completed"), eq37(runs.status, "partial")),
|
|
34079
34277
|
// Backfill must not replay probe runs as if they were real sweeps.
|
|
34080
34278
|
ne5(runs.trigger, RunTriggers.probe)
|
|
34081
34279
|
)
|
|
@@ -34108,7 +34306,7 @@ var IntelligenceService = class {
|
|
|
34108
34306
|
let wouldDeleteTotal = 0;
|
|
34109
34307
|
const existingByRunId = /* @__PURE__ */ new Map();
|
|
34110
34308
|
if (isDryRun && targetRuns.length > 0) {
|
|
34111
|
-
const rows = this.db.select({ runId: insights.runId }).from(insights).where(
|
|
34309
|
+
const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray14(insights.runId, targetRuns.map((r) => r.id))).all();
|
|
34112
34310
|
for (const r of rows) {
|
|
34113
34311
|
if (r.runId == null) continue;
|
|
34114
34312
|
existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
|
|
@@ -34154,7 +34352,7 @@ var IntelligenceService = class {
|
|
|
34154
34352
|
return { processed, skipped, totalInsights };
|
|
34155
34353
|
}
|
|
34156
34354
|
loadTrackedCompetitors(projectId) {
|
|
34157
|
-
return this.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
34355
|
+
return this.db.select({ domain: competitors.domain }).from(competitors).where(eq37(competitors.projectId, projectId)).all().map((r) => r.domain);
|
|
34158
34356
|
}
|
|
34159
34357
|
/**
|
|
34160
34358
|
* Wipe transition signals from an analysis result while keeping health.
|
|
@@ -34175,15 +34373,15 @@ var IntelligenceService = class {
|
|
|
34175
34373
|
}
|
|
34176
34374
|
persistResult(result, runId, projectId) {
|
|
34177
34375
|
const previouslyDismissed = /* @__PURE__ */ new Set();
|
|
34178
|
-
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(
|
|
34376
|
+
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq37(insights.runId, runId)).all();
|
|
34179
34377
|
for (const row of existingInsights) {
|
|
34180
34378
|
if (row.dismissed) {
|
|
34181
34379
|
previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
|
|
34182
34380
|
}
|
|
34183
34381
|
}
|
|
34184
34382
|
this.db.transaction((tx) => {
|
|
34185
|
-
tx.delete(insights).where(
|
|
34186
|
-
tx.delete(healthSnapshots).where(
|
|
34383
|
+
tx.delete(insights).where(eq37(insights.runId, runId)).run();
|
|
34384
|
+
tx.delete(healthSnapshots).where(eq37(healthSnapshots.runId, runId)).run();
|
|
34187
34385
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
34188
34386
|
for (const insight of result.insights) {
|
|
34189
34387
|
const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
|
|
@@ -34236,28 +34434,28 @@ var IntelligenceService = class {
|
|
|
34236
34434
|
applySeverityTiering(rawInsights, excludeRunId, projectId) {
|
|
34237
34435
|
const regressions = rawInsights.filter((i) => i.type === "regression");
|
|
34238
34436
|
if (regressions.length === 0) return rawInsights;
|
|
34239
|
-
const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(
|
|
34437
|
+
const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq37(gscSearchData.projectId, projectId)).all();
|
|
34240
34438
|
const gscConnected = gscRows.length > 0;
|
|
34241
34439
|
const gscImpressionsByQuery = /* @__PURE__ */ new Map();
|
|
34242
34440
|
for (const row of gscRows) {
|
|
34243
34441
|
const key = row.query.toLowerCase();
|
|
34244
34442
|
gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
|
|
34245
34443
|
}
|
|
34246
|
-
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
34444
|
+
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq37(projects.id, projectId)).get();
|
|
34247
34445
|
const locationCount = Math.max(
|
|
34248
34446
|
1,
|
|
34249
34447
|
(projectRow?.locations ?? []).length
|
|
34250
34448
|
);
|
|
34251
34449
|
const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
|
|
34252
34450
|
const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
34253
|
-
|
|
34254
|
-
|
|
34255
|
-
|
|
34256
|
-
or5(
|
|
34451
|
+
and27(
|
|
34452
|
+
eq37(runs.projectId, projectId),
|
|
34453
|
+
eq37(runs.kind, RunKinds["answer-visibility"]),
|
|
34454
|
+
or5(eq37(runs.status, "completed"), eq37(runs.status, "partial")),
|
|
34257
34455
|
// Defensive — see top of file.
|
|
34258
34456
|
ne5(runs.trigger, RunTriggers.probe)
|
|
34259
34457
|
)
|
|
34260
|
-
).orderBy(
|
|
34458
|
+
).orderBy(desc18(runs.createdAt), desc18(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
|
|
34261
34459
|
const recentGroups = groupRunsByCreatedAt(recentRunRows);
|
|
34262
34460
|
const recentRunIds = [];
|
|
34263
34461
|
const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
|
|
@@ -34273,7 +34471,7 @@ var IntelligenceService = class {
|
|
|
34273
34471
|
const haveHistory = recentRunIds.length > 0;
|
|
34274
34472
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
34275
34473
|
if (haveHistory) {
|
|
34276
|
-
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(
|
|
34474
|
+
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and27(eq37(insights.type, "regression"), inArray14(insights.runId, recentRunIds))).all();
|
|
34277
34475
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
34278
34476
|
for (const row of priorRows) {
|
|
34279
34477
|
if (!row.runId) continue;
|
|
@@ -34302,7 +34500,7 @@ var IntelligenceService = class {
|
|
|
34302
34500
|
});
|
|
34303
34501
|
}
|
|
34304
34502
|
buildRunData(runId, projectId, completedAt, location = null) {
|
|
34305
|
-
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(
|
|
34503
|
+
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq37(projects.id, projectId)).get();
|
|
34306
34504
|
const projectDomains = projectDomainRow ? effectiveDomains({
|
|
34307
34505
|
canonicalDomain: projectDomainRow.canonicalDomain,
|
|
34308
34506
|
ownedDomains: projectDomainRow.ownedDomains
|
|
@@ -34319,7 +34517,7 @@ var IntelligenceService = class {
|
|
|
34319
34517
|
citedDomains: querySnapshots.citedDomains,
|
|
34320
34518
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
34321
34519
|
snapshotLocation: querySnapshots.location
|
|
34322
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
34520
|
+
}).from(querySnapshots).leftJoin(queries, eq37(querySnapshots.queryId, queries.id)).where(eq37(querySnapshots.runId, runId)).all();
|
|
34323
34521
|
const snapshots = [];
|
|
34324
34522
|
let orphanCount = 0;
|
|
34325
34523
|
for (const r of rows) {
|