@canonry/canonry 4.117.1 → 4.117.2

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.
Files changed (26) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +21 -0
  2. package/assets/assets/{BacklinksPage-Bkafm10m.js → BacklinksPage-CschVQxC.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-NV_GgqFn.js → ChartPrimitives-D4782Ifx.js} +1 -1
  4. package/assets/assets/ProjectPage-BSGTpBUJ.js +7 -0
  5. package/assets/assets/{RunRow-df0d24S6.js → RunRow-ggNGlwdN.js} +1 -1
  6. package/assets/assets/{RunsPage-CU6-4YTM.js → RunsPage-Bfp0K6Ry.js} +1 -1
  7. package/assets/assets/{SettingsPage-BKw6B6cd.js → SettingsPage-Cy9SlBJX.js} +1 -1
  8. package/assets/assets/{TrafficPage-CzpiYHKj.js → TrafficPage-1S2PzSHB.js} +1 -1
  9. package/assets/assets/{TrafficSourceDetailPage-CC5nuqM1.js → TrafficSourceDetailPage-CqXCoaod.js} +1 -1
  10. package/assets/assets/{arrow-left-CbQcW2pL.js → arrow-left-kCK5M8Bp.js} +1 -1
  11. package/assets/assets/{extract-error-message-CvqIZ-le.js → extract-error-message-C82LAW6q.js} +1 -1
  12. package/assets/assets/index-DrDNyUhv.css +1 -0
  13. package/assets/assets/{index-LjcWHaNZ.js → index-i-t64RXw.js} +74 -74
  14. package/assets/assets/{trash-2-3J2u0woR.js → trash-2-8Pe4v1T0.js} +1 -1
  15. package/assets/index.html +2 -2
  16. package/dist/{chunk-BMBHUFMI.js → chunk-4PMMEFME.js} +42 -1
  17. package/dist/{chunk-V4SW7SVX.js → chunk-BMPIV35C.js} +696 -417
  18. package/dist/{chunk-IAJ5TQ4S.js → chunk-EZME2J2G.js} +1196 -1140
  19. package/dist/{chunk-NY7SC4ES.js → chunk-F2SRZJMY.js} +4 -4
  20. package/dist/cli.js +121 -77
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-C5YLASIP.js → intelligence-service-GNOE4J35.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +13 -13
  25. package/assets/assets/ProjectPage-0-NnRxys.js +0 -7
  26. package/assets/assets/index-B4R1-8hR.css +0 -1
@@ -220,11 +220,14 @@ import {
220
220
  resolveConfigSpecQueries,
221
221
  resolveLocations,
222
222
  resolveSnapshotRequestQueries,
223
+ resultsExportDtoSchema,
223
224
  runDetailDtoSchema,
224
225
  runDtoSchema,
225
226
  runInProgress,
226
227
  runNotCancellable,
228
+ runStatusSchema,
227
229
  runTriggerRequestSchema,
230
+ runTriggerSchema,
228
231
  runtimeStateMissing,
229
232
  schedulableRunKindSchema,
230
233
  scheduleDtoSchema,
@@ -280,10 +283,10 @@ import {
280
283
  wordpressSchemaDeployResultDtoSchema,
281
284
  wordpressSchemaStatusResultDtoSchema,
282
285
  wordpressStatusDtoSchema
283
- } from "./chunk-IAJ5TQ4S.js";
286
+ } from "./chunk-EZME2J2G.js";
284
287
 
285
288
  // src/intelligence-service.ts
286
- import { eq as eq39, desc as desc18, asc as asc6, and as and30, ne as ne5, or as or6, inArray as inArray15, gte as gte7, lte as lte4 } from "drizzle-orm";
289
+ import { eq as eq40, desc as desc18, asc as asc7, and as and31, ne as ne5, or as or6, inArray as inArray15, gte as gte8, lte as lte5 } from "drizzle-orm";
287
290
 
288
291
  // ../db/src/client.ts
289
292
  import { mkdirSync } from "fs";
@@ -14239,8 +14242,226 @@ async function visibilityStatsRoutes(app) {
14239
14242
  });
14240
14243
  }
14241
14244
 
14245
+ // ../api-routes/src/results-export.ts
14246
+ import { and as and15, asc as asc3, eq as eq19, gte as gte3, lte as lte2 } from "drizzle-orm";
14247
+ var CSV_COLUMNS = [
14248
+ "export_schema_version",
14249
+ "project_id",
14250
+ "project_name",
14251
+ "project_display_name",
14252
+ "canonical_domain",
14253
+ "run_id",
14254
+ "run_kind",
14255
+ "run_status",
14256
+ "run_trigger",
14257
+ "run_created_at",
14258
+ "run_started_at",
14259
+ "run_finished_at",
14260
+ "snapshot_id",
14261
+ "snapshot_created_at",
14262
+ "query_id",
14263
+ "query",
14264
+ "provider",
14265
+ "model",
14266
+ "location",
14267
+ "citation_state",
14268
+ "cited",
14269
+ "answer_mentioned",
14270
+ "mention_state",
14271
+ "cited_domains_json",
14272
+ "competitor_overlap_json",
14273
+ "recommended_competitors_json",
14274
+ "answer_text",
14275
+ "grounding_sources_json",
14276
+ "search_queries_json"
14277
+ ];
14278
+ function parseFormat(value) {
14279
+ if (value === void 0 || value === "" || value === "json") return "json";
14280
+ if (value === "csv") return "csv";
14281
+ throw validationError('"format" must be "json" or "csv"');
14282
+ }
14283
+ function parseBoolean(value, name) {
14284
+ if (value === void 0 || value === "") return false;
14285
+ if (value === "true" || value === "1") return true;
14286
+ if (value === "false" || value === "0") return false;
14287
+ throw validationError(`"${name}" must be "true" or "false"`);
14288
+ }
14289
+ function parseDateBounds(since, until) {
14290
+ let sinceIso = null;
14291
+ let untilIso = null;
14292
+ if (since !== void 0 && since !== "") {
14293
+ const ms = Date.parse(since);
14294
+ if (Number.isNaN(ms)) throw validationError('"since" must be an ISO 8601 date/time');
14295
+ sinceIso = new Date(ms).toISOString();
14296
+ }
14297
+ if (until !== void 0 && until !== "") {
14298
+ const ms = parseInclusiveEndMs(until);
14299
+ if (ms === null) throw validationError('"until" must be an ISO 8601 date/time');
14300
+ untilIso = new Date(ms).toISOString();
14301
+ }
14302
+ if (sinceIso !== null && untilIso !== null && untilIso < sinceIso) {
14303
+ throw validationError('"until" must be on or after "since"');
14304
+ }
14305
+ return { sinceIso, untilIso };
14306
+ }
14307
+ function readEvidence(rawResponse) {
14308
+ const parsed = parseJsonColumn(rawResponse, {});
14309
+ const groundingSources = Array.isArray(parsed.groundingSources) ? parsed.groundingSources.filter(
14310
+ (source) => typeof source === "object" && source !== null && typeof source.uri === "string"
14311
+ ).map((source) => ({ uri: source.uri, title: typeof source.title === "string" ? source.title : "" })) : [];
14312
+ const searchQueries = Array.isArray(parsed.searchQueries) ? parsed.searchQueries.filter((query) => typeof query === "string") : [];
14313
+ return { groundingSources, searchQueries };
14314
+ }
14315
+ function recordMentionState(answerMentioned) {
14316
+ if (answerMentioned === null) return null;
14317
+ return answerMentioned ? "mentioned" : "not-mentioned";
14318
+ }
14319
+ function spreadsheetSafe(value) {
14320
+ return /^[\t\r ]*[=+\-@]/.test(value) ? `'${value}` : value;
14321
+ }
14322
+ function csvCell(value) {
14323
+ if (value === null) return "";
14324
+ const text2 = typeof value === "string" ? spreadsheetSafe(value) : String(value);
14325
+ return /[",\r\n]/.test(text2) ? `"${text2.replace(/"/g, '""')}"` : text2;
14326
+ }
14327
+ function renderResultsExportCsv(report) {
14328
+ const rows = report.records.map((record) => [
14329
+ report.schemaVersion,
14330
+ report.project.id,
14331
+ report.project.name,
14332
+ report.project.displayName,
14333
+ report.project.canonicalDomain,
14334
+ record.runId,
14335
+ record.runKind,
14336
+ record.runStatus,
14337
+ record.runTrigger,
14338
+ record.runCreatedAt,
14339
+ record.runStartedAt,
14340
+ record.runFinishedAt,
14341
+ record.snapshotId,
14342
+ record.snapshotCreatedAt,
14343
+ record.queryId,
14344
+ record.query,
14345
+ record.provider,
14346
+ record.model,
14347
+ record.location,
14348
+ record.citationState,
14349
+ record.cited,
14350
+ record.answerMentioned,
14351
+ record.mentionState,
14352
+ JSON.stringify(record.citedDomains),
14353
+ JSON.stringify(record.competitorOverlap),
14354
+ JSON.stringify(record.recommendedCompetitors),
14355
+ record.answerText,
14356
+ JSON.stringify(record.groundingSources),
14357
+ JSON.stringify(record.searchQueries)
14358
+ ].map(csvCell).join(","));
14359
+ return [CSV_COLUMNS.join(","), ...rows].join("\r\n") + "\r\n";
14360
+ }
14361
+ function filenameFor(projectName, format) {
14362
+ const safeName = projectName.replace(/[^\w.-]+/gi, "_") || "project";
14363
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
14364
+ return `canonry-results-${safeName}-${date}.${format}`;
14365
+ }
14366
+ async function resultsExportRoutes(app) {
14367
+ app.get("/projects/:name/results/export", async (request, reply) => {
14368
+ const project = resolveProject(app.db, request.params.name);
14369
+ const format = parseFormat(request.query.format);
14370
+ const includeProbes = parseBoolean(request.query.includeProbes, "includeProbes");
14371
+ const { sinceIso, untilIso } = parseDateBounds(request.query.since, request.query.until);
14372
+ const conditions = [
14373
+ eq19(runs.projectId, project.id),
14374
+ eq19(runs.kind, RunKinds["answer-visibility"]),
14375
+ ...includeProbes ? [] : [notProbeRun()],
14376
+ ...sinceIso ? [gte3(runs.createdAt, sinceIso)] : [],
14377
+ ...untilIso ? [lte2(runs.createdAt, untilIso)] : []
14378
+ ];
14379
+ const rows = app.db.select({
14380
+ runId: runs.id,
14381
+ runStatus: runs.status,
14382
+ runTrigger: runs.trigger,
14383
+ runCreatedAt: runs.createdAt,
14384
+ runStartedAt: runs.startedAt,
14385
+ runFinishedAt: runs.finishedAt,
14386
+ snapshotId: querySnapshots.id,
14387
+ snapshotCreatedAt: querySnapshots.createdAt,
14388
+ queryId: querySnapshots.queryId,
14389
+ queryText: querySnapshots.queryText,
14390
+ currentQuery: queries.query,
14391
+ provider: querySnapshots.provider,
14392
+ model: querySnapshots.model,
14393
+ location: querySnapshots.location,
14394
+ citationState: querySnapshots.citationState,
14395
+ answerMentioned: querySnapshots.answerMentioned,
14396
+ citedDomains: querySnapshots.citedDomains,
14397
+ competitorOverlap: querySnapshots.competitorOverlap,
14398
+ recommendedCompetitors: querySnapshots.recommendedCompetitors,
14399
+ answerText: querySnapshots.answerText,
14400
+ rawResponse: querySnapshots.rawResponse
14401
+ }).from(querySnapshots).innerJoin(runs, eq19(querySnapshots.runId, runs.id)).leftJoin(queries, eq19(querySnapshots.queryId, queries.id)).where(and15(...conditions)).orderBy(asc3(runs.createdAt), asc3(runs.id), asc3(querySnapshots.createdAt), asc3(querySnapshots.id)).all();
14402
+ const records = rows.map((row) => {
14403
+ const evidence = readEvidence(row.rawResponse);
14404
+ const citationState = row.citationState === CitationStates.cited ? CitationStates.cited : CitationStates["not-cited"];
14405
+ return {
14406
+ runId: row.runId,
14407
+ runKind: RunKinds["answer-visibility"],
14408
+ runStatus: runStatusSchema.parse(row.runStatus),
14409
+ runTrigger: runTriggerSchema.parse(row.runTrigger),
14410
+ runCreatedAt: row.runCreatedAt,
14411
+ runStartedAt: row.runStartedAt,
14412
+ runFinishedAt: row.runFinishedAt,
14413
+ snapshotId: row.snapshotId,
14414
+ snapshotCreatedAt: row.snapshotCreatedAt,
14415
+ queryId: row.queryId,
14416
+ query: row.queryText ?? row.currentQuery,
14417
+ provider: row.provider,
14418
+ model: row.model,
14419
+ location: row.location,
14420
+ citationState,
14421
+ cited: citationState === CitationStates.cited,
14422
+ answerMentioned: row.answerMentioned,
14423
+ mentionState: recordMentionState(row.answerMentioned),
14424
+ citedDomains: row.citedDomains,
14425
+ competitorOverlap: row.competitorOverlap,
14426
+ recommendedCompetitors: row.recommendedCompetitors,
14427
+ answerText: row.answerText,
14428
+ groundingSources: evidence.groundingSources,
14429
+ searchQueries: evidence.searchQueries
14430
+ };
14431
+ });
14432
+ const dto = {
14433
+ schemaVersion: "canonry.results-export/v1",
14434
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
14435
+ project: {
14436
+ id: project.id,
14437
+ name: project.name,
14438
+ displayName: project.displayName,
14439
+ canonicalDomain: project.canonicalDomain,
14440
+ country: project.country,
14441
+ language: project.language
14442
+ },
14443
+ filters: {
14444
+ since: request.query.since || null,
14445
+ until: request.query.until || null,
14446
+ includeProbes
14447
+ },
14448
+ recordCount: records.length,
14449
+ records
14450
+ };
14451
+ reply.header("Content-Disposition", `attachment; filename="${filenameFor(project.name, format)}"`);
14452
+ reply.header("Cache-Control", "private, no-store");
14453
+ reply.header("X-Content-Type-Options", "nosniff");
14454
+ if (format === "csv") {
14455
+ reply.type("text/csv; charset=utf-8");
14456
+ return reply.send(renderResultsExportCsv(dto));
14457
+ }
14458
+ reply.type("application/json; charset=utf-8");
14459
+ return reply.send(dto);
14460
+ });
14461
+ }
14462
+
14242
14463
  // ../api-routes/src/composites.ts
14243
- import { eq as eq19, and as and15, desc as desc9, sql as sql8, like as like2, or as or4, inArray as inArray10 } from "drizzle-orm";
14464
+ import { eq as eq20, and as and16, desc as desc9, sql as sql8, like as like2, or as or4, inArray as inArray10 } from "drizzle-orm";
14244
14465
  var TOP_INSIGHT_LIMIT = 5;
14245
14466
  var SEARCH_HIT_HARD_LIMIT = 50;
14246
14467
  var SEARCH_SNIPPET_RADIUS = 80;
@@ -14258,7 +14479,7 @@ async function compositeRoutes(app) {
14258
14479
  const project = resolveProject(app.db, request.params.name);
14259
14480
  const filterLocation = (request.query.location ?? "").trim() || null;
14260
14481
  const sinceIso = parseSinceFilter(request.query.since);
14261
- const allRunsRaw = app.db.select().from(runs).where(and15(eq19(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
14482
+ const allRunsRaw = app.db.select().from(runs).where(and16(eq20(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
14262
14483
  const allRuns = allRunsRaw.filter((r) => runMatchesFilters(r, filterLocation, sinceIso));
14263
14484
  const totalRuns = allRuns.length;
14264
14485
  const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
@@ -14271,15 +14492,15 @@ async function compositeRoutes(app) {
14271
14492
  const previousVisibilityRun = pickGroupRepresentative(previousVisRunGroup);
14272
14493
  const latestRunRow = allRuns[0] ?? null;
14273
14494
  const latestRun = latestRunRow ? { totalRuns, run: summarizeRun(latestRunRow) } : { totalRuns: 0, run: null };
14274
- const healthRow = app.db.select().from(healthSnapshots).where(eq19(healthSnapshots.projectId, project.id)).orderBy(desc9(healthSnapshots.createdAt)).limit(1).get();
14495
+ const healthRow = app.db.select().from(healthSnapshots).where(eq20(healthSnapshots.projectId, project.id)).orderBy(desc9(healthSnapshots.createdAt)).limit(1).get();
14275
14496
  const health = healthRow ? mapHealthRow2(healthRow) : null;
14276
- const insightRows = app.db.select().from(insights).where(eq19(insights.projectId, project.id)).orderBy(desc9(insights.createdAt)).all();
14497
+ const insightRows = app.db.select().from(insights).where(eq20(insights.projectId, project.id)).orderBy(desc9(insights.createdAt)).all();
14277
14498
  const topInsights = insightRows.filter((row) => !row.dismissed).slice(0, TOP_INSIGHT_LIMIT).map(mapInsightRow2);
14278
14499
  const sparklineRunIds = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => r.id);
14279
14500
  const snapshotRunIds = new Set(sparklineRunIds);
14280
14501
  for (const run of latestVisRunGroup) snapshotRunIds.add(run.id);
14281
14502
  for (const run of previousVisRunGroup) snapshotRunIds.add(run.id);
14282
- const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq19(queries.projectId, project.id)).all();
14503
+ const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq20(queries.projectId, project.id)).all();
14283
14504
  const queryIdByText = new Map(projectQueries.map((q) => [normalizeQueryText(q.query), q.id]));
14284
14505
  const snapshotsByRun = loadSnapshotsByRunIds(app, [...snapshotRunIds], queryIdByText);
14285
14506
  const latestSnapshots = latestVisRunGroup.flatMap((r) => snapshotsByRun.get(r.id) ?? []);
@@ -14295,7 +14516,7 @@ async function compositeRoutes(app) {
14295
14516
  trackedPrevious,
14296
14517
  previousVisibilityRun?.createdAt ?? null
14297
14518
  );
14298
- const competitorRows = app.db.select().from(competitors).where(eq19(competitors.projectId, project.id)).all();
14519
+ const competitorRows = app.db.select().from(competitors).where(eq20(competitors.projectId, project.id)).all();
14299
14520
  const queryLookup = { byId: new Map(projectQueries.map((q) => [q.id, q.query])) };
14300
14521
  for (const snapshots of snapshotsByRun.values()) {
14301
14522
  for (const snapshot of snapshots) {
@@ -14413,9 +14634,9 @@ async function compositeRoutes(app) {
14413
14634
  citedDomains: querySnapshots.citedDomains,
14414
14635
  rawResponse: querySnapshots.rawResponse,
14415
14636
  createdAt: querySnapshots.createdAt
14416
- }).from(querySnapshots).innerJoin(queries, eq19(querySnapshots.queryId, queries.id)).where(
14417
- and15(
14418
- eq19(queries.projectId, project.id),
14637
+ }).from(querySnapshots).innerJoin(queries, eq20(querySnapshots.queryId, queries.id)).where(
14638
+ and16(
14639
+ eq20(queries.projectId, project.id),
14419
14640
  or4(
14420
14641
  sql8`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
14421
14642
  sql8`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
@@ -14425,8 +14646,8 @@ async function compositeRoutes(app) {
14425
14646
  )
14426
14647
  ).orderBy(desc9(querySnapshots.createdAt)).limit(limit + 1).all());
14427
14648
  const insightMatches = app.db.select().from(insights).where(
14428
- and15(
14429
- eq19(insights.projectId, project.id),
14649
+ and16(
14650
+ eq20(insights.projectId, project.id),
14430
14651
  or4(
14431
14652
  like2(insights.title, pattern),
14432
14653
  like2(insights.query, pattern),
@@ -14642,8 +14863,8 @@ function buildSuggestedQueriesFromGsc(app, projectId, trackedQueries) {
14642
14863
  // NULLIF guards the degenerate impressions=0 case (SQLite returns NULL,
14643
14864
  // which the JS coerces to 0 — caught by the impression floor anyway).
14644
14865
  avgPosition: sql8`COALESCE(SUM(${gscSearchData.position} * ${gscSearchData.impressions}) * 1.0 / NULLIF(SUM(${gscSearchData.impressions}), 0), 0)`
14645
- }).from(gscSearchData).where(and15(
14646
- eq19(gscSearchData.projectId, projectId),
14866
+ }).from(gscSearchData).where(and16(
14867
+ eq20(gscSearchData.projectId, projectId),
14647
14868
  sql8`${gscSearchData.date} >= ${cutoff}`,
14648
14869
  sql8`${gscSearchData.impressions} > 0`
14649
14870
  )).groupBy(gscSearchData.query).orderBy(sql8`SUM(${gscSearchData.impressions}) DESC`).limit(100).all();
@@ -14666,8 +14887,8 @@ function buildIndexCoverageScore(app, projectId) {
14666
14887
  tooltip,
14667
14888
  trend: []
14668
14889
  };
14669
- const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq19(gscCoverageSnapshots.projectId, projectId)).orderBy(desc9(gscCoverageSnapshots.date)).limit(1).get();
14670
- const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq19(bingCoverageSnapshots.projectId, projectId)).orderBy(desc9(bingCoverageSnapshots.date)).limit(1).get();
14890
+ const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq20(gscCoverageSnapshots.projectId, projectId)).orderBy(desc9(gscCoverageSnapshots.date)).limit(1).get();
14891
+ const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq20(bingCoverageSnapshots.projectId, projectId)).orderBy(desc9(bingCoverageSnapshots.date)).limit(1).get();
14671
14892
  const chosen = pickIndexCoverageRow(gscRow, bingRow);
14672
14893
  if (!chosen) return empty;
14673
14894
  const total = chosen.indexed + chosen.notIndexed;
@@ -14693,7 +14914,7 @@ function countGoogleDeindexedUrls(app, projectId) {
14693
14914
  url: gscUrlInspections.url,
14694
14915
  indexingState: gscUrlInspections.indexingState,
14695
14916
  inspectedAt: gscUrlInspections.inspectedAt
14696
- }).from(gscUrlInspections).where(eq19(gscUrlInspections.projectId, projectId)).orderBy(desc9(gscUrlInspections.inspectedAt)).all();
14917
+ }).from(gscUrlInspections).where(eq20(gscUrlInspections.projectId, projectId)).orderBy(desc9(gscUrlInspections.inspectedAt)).all();
14697
14918
  if (rows.length === 0) return 0;
14698
14919
  const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
14699
14920
  const historyByUrl = /* @__PURE__ */ new Map();
@@ -15022,6 +15243,7 @@ var SCHEMA_TABLE = {
15022
15243
  ProjectOverviewDto: projectOverviewDtoSchema,
15023
15244
  ProjectReportDto: projectReportDtoSchema,
15024
15245
  QueryDto: queryDtoSchema,
15246
+ ResultsExportDto: resultsExportDtoSchema,
15025
15247
  RunDetailDto: runDetailDtoSchema,
15026
15248
  RunDto: runDtoSchema,
15027
15249
  SchedulableRunKind: schedulableRunKindSchema,
@@ -15526,6 +15748,62 @@ var routeCatalog = [
15526
15748
  404: errorResponse("Project not found.")
15527
15749
  }
15528
15750
  },
15751
+ {
15752
+ method: "get",
15753
+ path: "/api/v1/projects/{name}/results/export",
15754
+ summary: "Download historical answer-engine results",
15755
+ tags: ["exports"],
15756
+ description: "Downloads one record per persisted answer-visibility query \xD7 provider observation. Citation and mention stay independent: `citationState` describes source-list attribution, while nullable `answerMentioned` describes answer-text presence. Excludes probe runs by default. JSON is a versioned portable artifact; CSV is its flat spreadsheet representation. Raw provider payloads, credentials, and local paths are never included.",
15757
+ parameters: [
15758
+ nameParameter,
15759
+ {
15760
+ name: "format",
15761
+ in: "query",
15762
+ description: "Download format. Defaults to `json`.",
15763
+ schema: { type: "string", enum: ["json", "csv"], default: "json" }
15764
+ },
15765
+ {
15766
+ name: "since",
15767
+ in: "query",
15768
+ description: "Inclusive ISO 8601 lower bound on run creation time.",
15769
+ schema: stringSchema
15770
+ },
15771
+ {
15772
+ name: "until",
15773
+ in: "query",
15774
+ description: "Inclusive ISO 8601 upper bound on run creation time. A date-only value includes that whole UTC day.",
15775
+ schema: stringSchema
15776
+ },
15777
+ {
15778
+ name: "includeProbes",
15779
+ in: "query",
15780
+ description: "Include operator/agent probe runs. Defaults to `false`.",
15781
+ schema: booleanSchema
15782
+ }
15783
+ ],
15784
+ responses: {
15785
+ 200: {
15786
+ description: "Results attachment returned.",
15787
+ content: {
15788
+ // The query-selected attachment can be JSON or CSV. OpenAPI cannot
15789
+ // express that the `format` parameter selects a media type, so keep
15790
+ // the generated SDK honest with a union rather than promising JSON
15791
+ // to a caller that requested CSV.
15792
+ "application/json": {
15793
+ schema: {
15794
+ oneOf: [
15795
+ { $ref: "#/components/schemas/ResultsExportDto" },
15796
+ { type: "string" }
15797
+ ]
15798
+ }
15799
+ },
15800
+ "text/csv": { schema: { type: "string" } }
15801
+ }
15802
+ },
15803
+ 400: errorResponse("Invalid export filters."),
15804
+ 404: errorResponse("Project not found.")
15805
+ }
15806
+ },
15529
15807
  {
15530
15808
  method: "get",
15531
15809
  path: "/api/v1/projects/{name}/queries",
@@ -19432,7 +19710,7 @@ async function settingsRoutes(app, opts) {
19432
19710
 
19433
19711
  // ../api-routes/src/keys.ts
19434
19712
  import crypto13 from "crypto";
19435
- import { desc as desc10, eq as eq20 } from "drizzle-orm";
19713
+ import { desc as desc10, eq as eq21 } from "drizzle-orm";
19436
19714
  var KEYS_WRITE_SCOPE = "keys.write";
19437
19715
  function toApiKeyDto(row) {
19438
19716
  const scopes = Array.isArray(row.scopes) ? row.scopes : [];
@@ -19451,7 +19729,7 @@ function toApiKeyDto(row) {
19451
19729
  async function keysRoutes(app) {
19452
19730
  app.get("/keys", async (request) => {
19453
19731
  const scopedProjectId = request.apiKey?.projectId;
19454
- const rows = scopedProjectId ? app.db.select().from(apiKeys).where(eq20(apiKeys.projectId, scopedProjectId)).orderBy(desc10(apiKeys.createdAt)).all() : app.db.select().from(apiKeys).orderBy(desc10(apiKeys.createdAt)).all();
19732
+ const rows = scopedProjectId ? app.db.select().from(apiKeys).where(eq21(apiKeys.projectId, scopedProjectId)).orderBy(desc10(apiKeys.createdAt)).all() : app.db.select().from(apiKeys).orderBy(desc10(apiKeys.createdAt)).all();
19455
19733
  return { keys: rows.map(toApiKeyDto) };
19456
19734
  });
19457
19735
  app.get("/keys/self", async (request) => {
@@ -19459,7 +19737,7 @@ async function keysRoutes(app) {
19459
19737
  if (!id) {
19460
19738
  throw notFound("API key", "self");
19461
19739
  }
19462
- const row = app.db.select().from(apiKeys).where(eq20(apiKeys.id, id)).get();
19740
+ const row = app.db.select().from(apiKeys).where(eq21(apiKeys.id, id)).get();
19463
19741
  if (!row) {
19464
19742
  throw notFound("API key", id);
19465
19743
  }
@@ -19477,7 +19755,7 @@ async function keysRoutes(app) {
19477
19755
  throw forbidden("A project-scoped key can only mint keys scoped to its own project.");
19478
19756
  }
19479
19757
  if (projectId) {
19480
- const proj = app.db.select({ id: projects.id }).from(projects).where(eq20(projects.id, projectId)).get();
19758
+ const proj = app.db.select({ id: projects.id }).from(projects).where(eq21(projects.id, projectId)).get();
19481
19759
  if (!proj) {
19482
19760
  throw notFound("Project", projectId);
19483
19761
  }
@@ -19523,7 +19801,7 @@ async function keysRoutes(app) {
19523
19801
  app.post("/keys/:id/revoke", async (request) => {
19524
19802
  requireScope(request, KEYS_WRITE_SCOPE);
19525
19803
  const { id } = request.params;
19526
- const row = app.db.select().from(apiKeys).where(eq20(apiKeys.id, id)).get();
19804
+ const row = app.db.select().from(apiKeys).where(eq21(apiKeys.id, id)).get();
19527
19805
  if (!row) {
19528
19806
  throw notFound("API key", id);
19529
19807
  }
@@ -19539,7 +19817,7 @@ async function keysRoutes(app) {
19539
19817
  }
19540
19818
  const now = (/* @__PURE__ */ new Date()).toISOString();
19541
19819
  app.db.transaction((tx) => {
19542
- tx.update(apiKeys).set({ revokedAt: now }).where(eq20(apiKeys.id, id)).run();
19820
+ tx.update(apiKeys).set({ revokedAt: now }).where(eq21(apiKeys.id, id)).run();
19543
19821
  writeAuditLog(tx, auditFromRequest(request, {
19544
19822
  actor: "api",
19545
19823
  action: "api-key.revoked",
@@ -19612,7 +19890,7 @@ async function telemetryRoutes(app, opts) {
19612
19890
 
19613
19891
  // ../api-routes/src/schedules.ts
19614
19892
  import crypto14 from "crypto";
19615
- import { and as and16, eq as eq21 } from "drizzle-orm";
19893
+ import { and as and17, eq as eq22 } from "drizzle-orm";
19616
19894
  function parseKindParam(raw) {
19617
19895
  if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
19618
19896
  const parsed = schedulableRunKindSchema.safeParse(raw);
@@ -19639,7 +19917,7 @@ async function scheduleRoutes(app, opts) {
19639
19917
  if (!sourceId) {
19640
19918
  throw validationError('"sourceId" is required when kind is "traffic-sync"');
19641
19919
  }
19642
- const sourceRow = app.db.select().from(trafficSources).where(eq21(trafficSources.id, sourceId)).get();
19920
+ const sourceRow = app.db.select().from(trafficSources).where(eq22(trafficSources.id, sourceId)).get();
19643
19921
  if (!sourceRow || sourceRow.projectId !== project.id) {
19644
19922
  throw notFound("Traffic source", sourceId);
19645
19923
  }
@@ -19684,7 +19962,7 @@ async function scheduleRoutes(app, opts) {
19684
19962
  }
19685
19963
  const now = (/* @__PURE__ */ new Date()).toISOString();
19686
19964
  const enabledBool = enabled !== false;
19687
- const existing = app.db.select().from(schedules).where(and16(eq21(schedules.projectId, project.id), eq21(schedules.kind, kind))).get();
19965
+ const existing = app.db.select().from(schedules).where(and17(eq22(schedules.projectId, project.id), eq22(schedules.kind, kind))).get();
19688
19966
  if (existing) {
19689
19967
  app.db.update(schedules).set({
19690
19968
  cronExpr,
@@ -19694,7 +19972,7 @@ async function scheduleRoutes(app, opts) {
19694
19972
  sourceId: sourceId ?? null,
19695
19973
  enabled: enabledBool,
19696
19974
  updatedAt: now
19697
- }).where(eq21(schedules.id, existing.id)).run();
19975
+ }).where(eq22(schedules.id, existing.id)).run();
19698
19976
  } else {
19699
19977
  app.db.insert(schedules).values({
19700
19978
  id: crypto14.randomUUID(),
@@ -19718,13 +19996,13 @@ async function scheduleRoutes(app, opts) {
19718
19996
  diff: { kind, cronExpr, preset, timezone, providers, sourceId }
19719
19997
  });
19720
19998
  opts.onScheduleUpdated?.("upsert", project.id, kind);
19721
- const schedule = app.db.select().from(schedules).where(and16(eq21(schedules.projectId, project.id), eq21(schedules.kind, kind))).get();
19999
+ const schedule = app.db.select().from(schedules).where(and17(eq22(schedules.projectId, project.id), eq22(schedules.kind, kind))).get();
19722
20000
  return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
19723
20001
  });
19724
20002
  app.get("/projects/:name/schedule", async (request, reply) => {
19725
20003
  const project = resolveProject(app.db, request.params.name);
19726
20004
  const kind = parseKindParam(request.query?.kind);
19727
- const schedule = app.db.select().from(schedules).where(and16(eq21(schedules.projectId, project.id), eq21(schedules.kind, kind))).get();
20005
+ const schedule = app.db.select().from(schedules).where(and17(eq22(schedules.projectId, project.id), eq22(schedules.kind, kind))).get();
19728
20006
  if (!schedule) {
19729
20007
  throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
19730
20008
  }
@@ -19733,11 +20011,11 @@ async function scheduleRoutes(app, opts) {
19733
20011
  app.delete("/projects/:name/schedule", async (request, reply) => {
19734
20012
  const project = resolveProject(app.db, request.params.name);
19735
20013
  const kind = parseKindParam(request.query?.kind);
19736
- const schedule = app.db.select().from(schedules).where(and16(eq21(schedules.projectId, project.id), eq21(schedules.kind, kind))).get();
20014
+ const schedule = app.db.select().from(schedules).where(and17(eq22(schedules.projectId, project.id), eq22(schedules.kind, kind))).get();
19737
20015
  if (!schedule) {
19738
20016
  throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
19739
20017
  }
19740
- app.db.delete(schedules).where(eq21(schedules.id, schedule.id)).run();
20018
+ app.db.delete(schedules).where(eq22(schedules.id, schedule.id)).run();
19741
20019
  writeAuditLog(app.db, {
19742
20020
  projectId: project.id,
19743
20021
  actor: "api",
@@ -19770,7 +20048,7 @@ function formatSchedule(row) {
19770
20048
 
19771
20049
  // ../api-routes/src/notifications.ts
19772
20050
  import crypto15 from "crypto";
19773
- import { eq as eq22 } from "drizzle-orm";
20051
+ import { eq as eq23 } from "drizzle-orm";
19774
20052
  var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed", "insight.critical", "insight.high"];
19775
20053
  async function notificationRoutes(app, opts = {}) {
19776
20054
  const allowLoopback = opts.allowLoopbackWebhooks === true;
@@ -19810,22 +20088,22 @@ async function notificationRoutes(app, opts = {}) {
19810
20088
  diff: { channel, ...redactNotificationUrl(url), events }
19811
20089
  });
19812
20090
  return reply.status(201).send({
19813
- ...formatNotification(app.db.select().from(notifications).where(eq22(notifications.id, id)).get()),
20091
+ ...formatNotification(app.db.select().from(notifications).where(eq23(notifications.id, id)).get()),
19814
20092
  webhookSecret
19815
20093
  });
19816
20094
  });
19817
20095
  app.get("/projects/:name/notifications", async (request, reply) => {
19818
20096
  const project = resolveProject(app.db, request.params.name);
19819
- const rows = app.db.select().from(notifications).where(eq22(notifications.projectId, project.id)).all();
20097
+ const rows = app.db.select().from(notifications).where(eq23(notifications.projectId, project.id)).all();
19820
20098
  return reply.send(rows.map(formatNotification));
19821
20099
  });
19822
20100
  app.delete("/projects/:name/notifications/:id", async (request, reply) => {
19823
20101
  const project = resolveProject(app.db, request.params.name);
19824
- const notification = app.db.select().from(notifications).where(eq22(notifications.id, request.params.id)).get();
20102
+ const notification = app.db.select().from(notifications).where(eq23(notifications.id, request.params.id)).get();
19825
20103
  if (!notification || notification.projectId !== project.id) {
19826
20104
  throw notFound("Notification", request.params.id);
19827
20105
  }
19828
- app.db.delete(notifications).where(eq22(notifications.id, notification.id)).run();
20106
+ app.db.delete(notifications).where(eq23(notifications.id, notification.id)).run();
19829
20107
  writeAuditLog(app.db, {
19830
20108
  projectId: project.id,
19831
20109
  actor: "api",
@@ -19837,7 +20115,7 @@ async function notificationRoutes(app, opts = {}) {
19837
20115
  });
19838
20116
  app.post("/projects/:name/notifications/:id/test", async (request, reply) => {
19839
20117
  const project = resolveProject(app.db, request.params.name);
19840
- const notification = app.db.select().from(notifications).where(eq22(notifications.id, request.params.id)).get();
20118
+ const notification = app.db.select().from(notifications).where(eq23(notifications.id, request.params.id)).get();
19841
20119
  if (!notification || notification.projectId !== project.id) {
19842
20120
  throw notFound("Notification", request.params.id);
19843
20121
  }
@@ -19890,7 +20168,7 @@ function formatNotification(row) {
19890
20168
 
19891
20169
  // ../api-routes/src/google.ts
19892
20170
  import crypto19 from "crypto";
19893
- import { eq as eq23, and as and17, desc as desc11, sql as sql9, inArray as inArray11 } from "drizzle-orm";
20171
+ import { eq as eq24, and as and18, desc as desc11, sql as sql9, inArray as inArray11 } from "drizzle-orm";
19894
20172
 
19895
20173
  // ../api-routes/src/gbp-summary.ts
19896
20174
  function computeMetricTotals(rows) {
@@ -21730,7 +22008,7 @@ async function googleRoutes(app, opts) {
21730
22008
  if (!projectId) {
21731
22009
  return reply.status(400).send("Stale OAuth state \u2014 restart the connect flow.");
21732
22010
  }
21733
- const project = app.db.select().from(projects).where(eq23(projects.id, projectId)).get();
22011
+ const project = app.db.select().from(projects).where(eq24(projects.id, projectId)).get();
21734
22012
  if (!project) {
21735
22013
  return reply.status(400).send("Project no longer exists. Restart the connect flow.");
21736
22014
  }
@@ -21862,14 +22140,14 @@ async function googleRoutes(app, opts) {
21862
22140
  if (opts.onGscSyncRequested) {
21863
22141
  opts.onGscSyncRequested(runId, project.id, { days, full });
21864
22142
  }
21865
- const run = app.db.select().from(runs).where(eq23(runs.id, runId)).get();
22143
+ const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
21866
22144
  return run;
21867
22145
  });
21868
22146
  app.get("/projects/:name/google/gsc/performance", async (request) => {
21869
22147
  const project = resolveProject(app.db, request.params.name);
21870
22148
  const { startDate, endDate, query, page, limit, offset } = request.query;
21871
22149
  const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
21872
- const conditions = [eq23(gscSearchData.projectId, project.id)];
22150
+ const conditions = [eq24(gscSearchData.projectId, project.id)];
21873
22151
  if (startDate) conditions.push(sql9`${gscSearchData.date} >= ${startDate}`);
21874
22152
  else if (cutoffDate) conditions.push(sql9`${gscSearchData.date} >= ${cutoffDate}`);
21875
22153
  if (endDate) conditions.push(sql9`${gscSearchData.date} <= ${endDate}`);
@@ -21877,7 +22155,7 @@ async function googleRoutes(app, opts) {
21877
22155
  if (page) conditions.push(sql9`${gscSearchData.page} LIKE ${"%" + escapeLikePattern(page) + "%"} ESCAPE '\\'`);
21878
22156
  const limitVal = Math.max(parseInt(limit ?? "500", 10) || 0, 1);
21879
22157
  const offsetVal = Math.max(parseInt(offset ?? "0", 10) || 0, 0);
21880
- const rows = app.db.select().from(gscSearchData).where(and17(...conditions)).orderBy(desc11(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
22158
+ const rows = app.db.select().from(gscSearchData).where(and18(...conditions)).orderBy(desc11(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
21881
22159
  return rows.map((r) => ({
21882
22160
  date: r.date,
21883
22161
  query: r.query,
@@ -21897,7 +22175,7 @@ async function googleRoutes(app, opts) {
21897
22175
  const windowStart = startDate ?? cutoffDate ?? "";
21898
22176
  const windowEnd = endDate ?? "9999-12-31";
21899
22177
  const dailyTotals = readGscDailyTotals(app.db, project.id, windowStart, windowEnd);
21900
- const conditions = [eq23(gscSearchData.projectId, project.id)];
22178
+ const conditions = [eq24(gscSearchData.projectId, project.id)];
21901
22179
  if (startDate) conditions.push(sql9`${gscSearchData.date} >= ${startDate}`);
21902
22180
  else if (cutoffDate) conditions.push(sql9`${gscSearchData.date} >= ${cutoffDate}`);
21903
22181
  if (endDate) conditions.push(sql9`${gscSearchData.date} <= ${endDate}`);
@@ -21905,7 +22183,7 @@ async function googleRoutes(app, opts) {
21905
22183
  date: gscSearchData.date,
21906
22184
  clicks: sql9`COALESCE(SUM(${gscSearchData.clicks}), 0)`,
21907
22185
  impressions: sql9`COALESCE(SUM(${gscSearchData.impressions}), 0)`
21908
- }).from(gscSearchData).where(and17(...conditions)).groupBy(gscSearchData.date).orderBy(gscSearchData.date).all();
22186
+ }).from(gscSearchData).where(and18(...conditions)).groupBy(gscSearchData.date).orderBy(gscSearchData.date).all();
21909
22187
  const daily = mergeGscDailyTotalsWithFallback(
21910
22188
  dailyTotals,
21911
22189
  dimensionedRows.map((r) => ({
@@ -21996,9 +22274,9 @@ async function googleRoutes(app, opts) {
21996
22274
  app.get("/projects/:name/google/gsc/inspections", async (request) => {
21997
22275
  const project = resolveProject(app.db, request.params.name);
21998
22276
  const { url, limit } = request.query;
21999
- const conditions = [eq23(gscUrlInspections.projectId, project.id)];
22000
- if (url) conditions.push(eq23(gscUrlInspections.url, url));
22001
- const rows = app.db.select().from(gscUrlInspections).where(and17(...conditions)).orderBy(desc11(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
22277
+ const conditions = [eq24(gscUrlInspections.projectId, project.id)];
22278
+ if (url) conditions.push(eq24(gscUrlInspections.url, url));
22279
+ const rows = app.db.select().from(gscUrlInspections).where(and18(...conditions)).orderBy(desc11(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
22002
22280
  return rows.map((r) => ({
22003
22281
  id: r.id,
22004
22282
  url: r.url,
@@ -22017,7 +22295,7 @@ async function googleRoutes(app, opts) {
22017
22295
  });
22018
22296
  app.get("/projects/:name/google/gsc/deindexed", async (request) => {
22019
22297
  const project = resolveProject(app.db, request.params.name);
22020
- const allInspections = app.db.select().from(gscUrlInspections).where(eq23(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
22298
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq24(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
22021
22299
  const byUrl = /* @__PURE__ */ new Map();
22022
22300
  for (const row of allInspections) {
22023
22301
  const existing = byUrl.get(row.url);
@@ -22045,7 +22323,7 @@ async function googleRoutes(app, opts) {
22045
22323
  });
22046
22324
  app.get("/projects/:name/google/gsc/coverage", async (request) => {
22047
22325
  const project = resolveProject(app.db, request.params.name);
22048
- const allInspections = app.db.select().from(gscUrlInspections).where(eq23(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
22326
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq24(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
22049
22327
  const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
22050
22328
  const latestByUrl = /* @__PURE__ */ new Map();
22051
22329
  const historyByUrl = /* @__PURE__ */ new Map();
@@ -22094,7 +22372,7 @@ async function googleRoutes(app, opts) {
22094
22372
  const total = latestByUrl.size;
22095
22373
  const indexed = indexedUrls.length;
22096
22374
  const notIndexed = notIndexedUrls.length;
22097
- const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq23(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.createdAt)).limit(1).get();
22375
+ const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq24(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.createdAt)).limit(1).get();
22098
22376
  const lastSyncedAt = latestSnapshot?.createdAt ?? null;
22099
22377
  const formatRow = (r) => ({
22100
22378
  id: r.id,
@@ -22145,7 +22423,7 @@ async function googleRoutes(app, opts) {
22145
22423
  const project = resolveProject(app.db, request.params.name);
22146
22424
  const parsed = parseInt(request.query.limit ?? "90", 10);
22147
22425
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
22148
- const rows = app.db.select().from(gscCoverageSnapshots).where(eq23(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.date)).limit(limit).all();
22426
+ const rows = app.db.select().from(gscCoverageSnapshots).where(eq24(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.date)).limit(limit).all();
22149
22427
  return rows.map((r) => ({
22150
22428
  date: r.date,
22151
22429
  indexed: r.indexed,
@@ -22214,7 +22492,7 @@ async function googleRoutes(app, opts) {
22214
22492
  if (opts.onInspectSitemapRequested) {
22215
22493
  opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl });
22216
22494
  }
22217
- const run = app.db.select().from(runs).where(eq23(runs.id, runId)).get();
22495
+ const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
22218
22496
  return { sitemaps, primarySitemapUrl: sitemapUrl, run };
22219
22497
  });
22220
22498
  app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
@@ -22241,7 +22519,7 @@ async function googleRoutes(app, opts) {
22241
22519
  if (opts.onInspectSitemapRequested) {
22242
22520
  opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
22243
22521
  }
22244
- const run = app.db.select().from(runs).where(eq23(runs.id, runId)).get();
22522
+ const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
22245
22523
  return run;
22246
22524
  });
22247
22525
  app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
@@ -22288,7 +22566,7 @@ async function googleRoutes(app, opts) {
22288
22566
  const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
22289
22567
  let urlsToNotify = request.body?.urls ?? [];
22290
22568
  if (request.body?.allUnindexed) {
22291
- const allInspections = app.db.select().from(gscUrlInspections).where(eq23(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
22569
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq24(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
22292
22570
  const latestByUrl = /* @__PURE__ */ new Map();
22293
22571
  for (const row of allInspections) {
22294
22572
  if (!latestByUrl.has(row.url)) {
@@ -22406,7 +22684,7 @@ async function googleRoutes(app, opts) {
22406
22684
  };
22407
22685
  }
22408
22686
  function listSelectionResponse(projectId) {
22409
- const rows = app.db.select().from(gbpLocations).where(eq23(gbpLocations.projectId, projectId)).all();
22687
+ const rows = app.db.select().from(gbpLocations).where(eq24(gbpLocations.projectId, projectId)).all();
22410
22688
  const dtos = rows.map(rowToDto2);
22411
22689
  return {
22412
22690
  locations: dtos,
@@ -22415,15 +22693,15 @@ async function googleRoutes(app, opts) {
22415
22693
  };
22416
22694
  }
22417
22695
  function clearGbpProjectData(tx, projectId) {
22418
- tx.delete(gbpDailyMetrics).where(eq23(gbpDailyMetrics.projectId, projectId)).run();
22419
- tx.delete(gbpKeywordImpressions).where(eq23(gbpKeywordImpressions.projectId, projectId)).run();
22420
- tx.delete(gbpKeywordMonthly).where(eq23(gbpKeywordMonthly.projectId, projectId)).run();
22421
- tx.delete(gbpPlaceActions).where(eq23(gbpPlaceActions.projectId, projectId)).run();
22422
- tx.delete(gbpLodgingSnapshots).where(eq23(gbpLodgingSnapshots.projectId, projectId)).run();
22423
- tx.delete(gbpLocations).where(eq23(gbpLocations.projectId, projectId)).run();
22696
+ tx.delete(gbpDailyMetrics).where(eq24(gbpDailyMetrics.projectId, projectId)).run();
22697
+ tx.delete(gbpKeywordImpressions).where(eq24(gbpKeywordImpressions.projectId, projectId)).run();
22698
+ tx.delete(gbpKeywordMonthly).where(eq24(gbpKeywordMonthly.projectId, projectId)).run();
22699
+ tx.delete(gbpPlaceActions).where(eq24(gbpPlaceActions.projectId, projectId)).run();
22700
+ tx.delete(gbpLodgingSnapshots).where(eq24(gbpLodgingSnapshots.projectId, projectId)).run();
22701
+ tx.delete(gbpLocations).where(eq24(gbpLocations.projectId, projectId)).run();
22424
22702
  }
22425
22703
  function currentProjectAccount(projectId) {
22426
- const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq23(gbpLocations.projectId, projectId)).limit(1).get();
22704
+ const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq24(gbpLocations.projectId, projectId)).limit(1).get();
22427
22705
  return row?.accountName ?? null;
22428
22706
  }
22429
22707
  app.post("/projects/:name/gbp/locations/discover", async (request) => {
@@ -22493,7 +22771,7 @@ async function googleRoutes(app, opts) {
22493
22771
  app.db.transaction((tx) => {
22494
22772
  if (switching) clearGbpProjectData(tx, project.id);
22495
22773
  for (const remote of remoteLocations) {
22496
- const existing = tx.select().from(gbpLocations).where(and17(eq23(gbpLocations.projectId, project.id), eq23(gbpLocations.locationName, remote.name))).get();
22774
+ const existing = tx.select().from(gbpLocations).where(and18(eq24(gbpLocations.projectId, project.id), eq24(gbpLocations.locationName, remote.name))).get();
22497
22775
  const profile = buildLocationProfileFields(remote);
22498
22776
  if (existing) {
22499
22777
  tx.update(gbpLocations).set({
@@ -22506,7 +22784,7 @@ async function googleRoutes(app, opts) {
22506
22784
  mapsUri: remote.metadata?.mapsUri ?? null,
22507
22785
  ...profile,
22508
22786
  updatedAt: now
22509
- }).where(eq23(gbpLocations.id, existing.id)).run();
22787
+ }).where(eq24(gbpLocations.id, existing.id)).run();
22510
22788
  } else {
22511
22789
  tx.insert(gbpLocations).values({
22512
22790
  id: crypto19.randomUUID(),
@@ -22590,11 +22868,11 @@ async function googleRoutes(app, opts) {
22590
22868
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid selection request");
22591
22869
  }
22592
22870
  const { selected } = parsed.data;
22593
- const existing = app.db.select().from(gbpLocations).where(and17(eq23(gbpLocations.projectId, project.id), eq23(gbpLocations.locationName, locationName))).get();
22871
+ const existing = app.db.select().from(gbpLocations).where(and18(eq24(gbpLocations.projectId, project.id), eq24(gbpLocations.locationName, locationName))).get();
22594
22872
  if (!existing) throw notFound("GBP location", locationName);
22595
22873
  const now = (/* @__PURE__ */ new Date()).toISOString();
22596
22874
  app.db.transaction((tx) => {
22597
- tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq23(gbpLocations.id, existing.id)).run();
22875
+ tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq24(gbpLocations.id, existing.id)).run();
22598
22876
  writeAuditLog(tx, {
22599
22877
  projectId: project.id,
22600
22878
  actor: "api",
@@ -22603,7 +22881,7 @@ async function googleRoutes(app, opts) {
22603
22881
  entityId: locationName
22604
22882
  });
22605
22883
  });
22606
- const refreshed = app.db.select().from(gbpLocations).where(eq23(gbpLocations.id, existing.id)).get();
22884
+ const refreshed = app.db.select().from(gbpLocations).where(eq24(gbpLocations.id, existing.id)).get();
22607
22885
  return rowToDto2(refreshed);
22608
22886
  });
22609
22887
  app.delete("/projects/:name/gbp/connection", async (request, reply) => {
@@ -22647,10 +22925,10 @@ async function googleRoutes(app, opts) {
22647
22925
  });
22648
22926
  app.get("/projects/:name/gbp/metrics", async (request) => {
22649
22927
  const project = resolveProject(app.db, request.params.name);
22650
- const conditions = [eq23(gbpDailyMetrics.projectId, project.id)];
22651
- if (request.query.locationName) conditions.push(eq23(gbpDailyMetrics.locationName, request.query.locationName));
22652
- if (request.query.metric) conditions.push(eq23(gbpDailyMetrics.metric, request.query.metric));
22653
- const rows = app.db.select().from(gbpDailyMetrics).where(and17(...conditions)).orderBy(desc11(gbpDailyMetrics.date)).all();
22928
+ const conditions = [eq24(gbpDailyMetrics.projectId, project.id)];
22929
+ if (request.query.locationName) conditions.push(eq24(gbpDailyMetrics.locationName, request.query.locationName));
22930
+ if (request.query.metric) conditions.push(eq24(gbpDailyMetrics.metric, request.query.metric));
22931
+ const rows = app.db.select().from(gbpDailyMetrics).where(and18(...conditions)).orderBy(desc11(gbpDailyMetrics.date)).all();
22654
22932
  return {
22655
22933
  metrics: rows.map((r) => ({ locationName: r.locationName, date: r.date, metric: r.metric, value: r.value })),
22656
22934
  total: rows.length
@@ -22658,9 +22936,9 @@ async function googleRoutes(app, opts) {
22658
22936
  });
22659
22937
  app.get("/projects/:name/gbp/keywords", async (request) => {
22660
22938
  const project = resolveProject(app.db, request.params.name);
22661
- const conditions = [eq23(gbpKeywordImpressions.projectId, project.id)];
22662
- if (request.query.locationName) conditions.push(eq23(gbpKeywordImpressions.locationName, request.query.locationName));
22663
- const rows = app.db.select().from(gbpKeywordImpressions).where(and17(...conditions)).all();
22939
+ const conditions = [eq24(gbpKeywordImpressions.projectId, project.id)];
22940
+ if (request.query.locationName) conditions.push(eq24(gbpKeywordImpressions.locationName, request.query.locationName));
22941
+ const rows = app.db.select().from(gbpKeywordImpressions).where(and18(...conditions)).all();
22664
22942
  rows.sort((a, b) => (b.valueCount ?? -1) - (a.valueCount ?? -1));
22665
22943
  const thresholded = rows.filter((r) => r.valueThreshold !== null).length;
22666
22944
  return {
@@ -22678,9 +22956,9 @@ async function googleRoutes(app, opts) {
22678
22956
  });
22679
22957
  app.get("/projects/:name/gbp/place-actions", async (request) => {
22680
22958
  const project = resolveProject(app.db, request.params.name);
22681
- const conditions = [eq23(gbpPlaceActions.projectId, project.id)];
22682
- if (request.query.locationName) conditions.push(eq23(gbpPlaceActions.locationName, request.query.locationName));
22683
- const rows = app.db.select().from(gbpPlaceActions).where(and17(...conditions)).all();
22959
+ const conditions = [eq24(gbpPlaceActions.projectId, project.id)];
22960
+ if (request.query.locationName) conditions.push(eq24(gbpPlaceActions.locationName, request.query.locationName));
22961
+ const rows = app.db.select().from(gbpPlaceActions).where(and18(...conditions)).all();
22684
22962
  return {
22685
22963
  placeActions: rows.map((r) => ({
22686
22964
  locationName: r.locationName,
@@ -22695,9 +22973,9 @@ async function googleRoutes(app, opts) {
22695
22973
  });
22696
22974
  app.get("/projects/:name/gbp/lodging", async (request) => {
22697
22975
  const project = resolveProject(app.db, request.params.name);
22698
- const conditions = [eq23(gbpLodgingSnapshots.projectId, project.id)];
22699
- if (request.query.locationName) conditions.push(eq23(gbpLodgingSnapshots.locationName, request.query.locationName));
22700
- const rows = app.db.select().from(gbpLodgingSnapshots).where(and17(...conditions)).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
22976
+ const conditions = [eq24(gbpLodgingSnapshots.projectId, project.id)];
22977
+ if (request.query.locationName) conditions.push(eq24(gbpLodgingSnapshots.locationName, request.query.locationName));
22978
+ const rows = app.db.select().from(gbpLodgingSnapshots).where(and18(...conditions)).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
22701
22979
  const latestByLocation = /* @__PURE__ */ new Map();
22702
22980
  for (const row of rows) {
22703
22981
  if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
@@ -22712,9 +22990,9 @@ async function googleRoutes(app, opts) {
22712
22990
  });
22713
22991
  app.get("/projects/:name/gbp/attributes", async (request) => {
22714
22992
  const project = resolveProject(app.db, request.params.name);
22715
- const conditions = [eq23(gbpAttributesSnapshots.projectId, project.id)];
22716
- if (request.query.locationName) conditions.push(eq23(gbpAttributesSnapshots.locationName, request.query.locationName));
22717
- const rows = app.db.select().from(gbpAttributesSnapshots).where(and17(...conditions)).orderBy(desc11(gbpAttributesSnapshots.syncedAt)).all();
22993
+ const conditions = [eq24(gbpAttributesSnapshots.projectId, project.id)];
22994
+ if (request.query.locationName) conditions.push(eq24(gbpAttributesSnapshots.locationName, request.query.locationName));
22995
+ const rows = app.db.select().from(gbpAttributesSnapshots).where(and18(...conditions)).orderBy(desc11(gbpAttributesSnapshots.syncedAt)).all();
22718
22996
  const latestByLocation = /* @__PURE__ */ new Map();
22719
22997
  for (const row of rows) {
22720
22998
  if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
@@ -22732,9 +23010,9 @@ async function googleRoutes(app, opts) {
22732
23010
  });
22733
23011
  app.get("/projects/:name/gbp/places", async (request) => {
22734
23012
  const project = resolveProject(app.db, request.params.name);
22735
- const conditions = [eq23(gbpPlaceDetails.projectId, project.id)];
22736
- if (request.query.locationName) conditions.push(eq23(gbpPlaceDetails.locationName, request.query.locationName));
22737
- const rows = app.db.select().from(gbpPlaceDetails).where(and17(...conditions)).orderBy(desc11(gbpPlaceDetails.syncedAt)).all();
23013
+ const conditions = [eq24(gbpPlaceDetails.projectId, project.id)];
23014
+ if (request.query.locationName) conditions.push(eq24(gbpPlaceDetails.locationName, request.query.locationName));
23015
+ const rows = app.db.select().from(gbpPlaceDetails).where(and18(...conditions)).orderBy(desc11(gbpPlaceDetails.syncedAt)).all();
22738
23016
  const latestByLocation = /* @__PURE__ */ new Map();
22739
23017
  for (const row of rows) {
22740
23018
  if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
@@ -22753,7 +23031,7 @@ async function googleRoutes(app, opts) {
22753
23031
  app.get("/projects/:name/gbp/summary", async (request) => {
22754
23032
  const project = resolveProject(app.db, request.params.name);
22755
23033
  const locationName = request.query.locationName ?? null;
22756
- const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and17(eq23(gbpLocations.projectId, project.id), eq23(gbpLocations.selected, true))).all().map((r) => r.n);
23034
+ const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and18(eq24(gbpLocations.projectId, project.id), eq24(gbpLocations.selected, true))).all().map((r) => r.n);
22757
23035
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22758
23036
  if (locationNames.length === 0) {
22759
23037
  return buildGbpSummary({
@@ -22767,10 +23045,10 @@ async function googleRoutes(app, opts) {
22767
23045
  locationProfiles: []
22768
23046
  });
22769
23047
  }
22770
- const metricRows = app.db.select().from(gbpDailyMetrics).where(and17(eq23(gbpDailyMetrics.projectId, project.id), inArray11(gbpDailyMetrics.locationName, locationNames))).all();
22771
- const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and17(eq23(gbpKeywordImpressions.projectId, project.id), inArray11(gbpKeywordImpressions.locationName, locationNames))).all();
22772
- const placeActionRows = app.db.select().from(gbpPlaceActions).where(and17(eq23(gbpPlaceActions.projectId, project.id), inArray11(gbpPlaceActions.locationName, locationNames))).all();
22773
- const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and17(eq23(gbpLodgingSnapshots.projectId, project.id), inArray11(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
23048
+ const metricRows = app.db.select().from(gbpDailyMetrics).where(and18(eq24(gbpDailyMetrics.projectId, project.id), inArray11(gbpDailyMetrics.locationName, locationNames))).all();
23049
+ const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and18(eq24(gbpKeywordImpressions.projectId, project.id), inArray11(gbpKeywordImpressions.locationName, locationNames))).all();
23050
+ const placeActionRows = app.db.select().from(gbpPlaceActions).where(and18(eq24(gbpPlaceActions.projectId, project.id), inArray11(gbpPlaceActions.locationName, locationNames))).all();
23051
+ const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and18(eq24(gbpLodgingSnapshots.projectId, project.id), inArray11(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
22774
23052
  const latestLodgingByLocation = /* @__PURE__ */ new Map();
22775
23053
  for (const row of lodgingRows) {
22776
23054
  if (!latestLodgingByLocation.has(row.locationName)) {
@@ -22784,7 +23062,7 @@ async function googleRoutes(app, opts) {
22784
23062
  regularHours: gbpLocations.regularHours,
22785
23063
  primaryPhone: gbpLocations.primaryPhone,
22786
23064
  openStatus: gbpLocations.openStatus
22787
- }).from(gbpLocations).where(and17(eq23(gbpLocations.projectId, project.id), inArray11(gbpLocations.locationName, locationNames))).all();
23065
+ }).from(gbpLocations).where(and18(eq24(gbpLocations.projectId, project.id), inArray11(gbpLocations.locationName, locationNames))).all();
22788
23066
  return buildGbpSummary({
22789
23067
  locationName,
22790
23068
  locationCount: locationNames.length,
@@ -22800,7 +23078,7 @@ async function googleRoutes(app, opts) {
22800
23078
 
22801
23079
  // ../api-routes/src/ads.ts
22802
23080
  import crypto20 from "crypto";
22803
- import { eq as eq24, and as and18, asc as asc3, gte as gte3, lte as lte2, inArray as inArray12 } from "drizzle-orm";
23081
+ import { eq as eq25, and as and19, asc as asc4, gte as gte4, lte as lte3, inArray as inArray12 } from "drizzle-orm";
22804
23082
  function statusDto(row) {
22805
23083
  if (!row) return { connected: false };
22806
23084
  return {
@@ -22850,7 +23128,7 @@ async function adsRoutes(app, opts) {
22850
23128
  createdAt: existingCfg?.createdAt ?? now,
22851
23129
  updatedAt: now
22852
23130
  });
22853
- const existingRow = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
23131
+ const existingRow = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
22854
23132
  app.db.transaction((tx) => {
22855
23133
  if (existingRow) {
22856
23134
  tx.update(adsConnections).set({
@@ -22860,7 +23138,7 @@ async function adsRoutes(app, opts) {
22860
23138
  timezone: account.timezone,
22861
23139
  status: account.status,
22862
23140
  updatedAt: now
22863
- }).where(eq24(adsConnections.id, existingRow.id)).run();
23141
+ }).where(eq25(adsConnections.id, existingRow.id)).run();
22864
23142
  } else {
22865
23143
  tx.insert(adsConnections).values({
22866
23144
  id: crypto20.randomUUID(),
@@ -22882,16 +23160,16 @@ async function adsRoutes(app, opts) {
22882
23160
  entityId: account.id
22883
23161
  }));
22884
23162
  });
22885
- const row = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
23163
+ const row = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
22886
23164
  return statusDto(row);
22887
23165
  }
22888
23166
  );
22889
23167
  app.delete("/projects/:name/ads/connection", async (request) => {
22890
23168
  const project = resolveProject(app.db, request.params.name);
22891
- const row = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
23169
+ const row = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
22892
23170
  if (row) {
22893
23171
  app.db.transaction((tx) => {
22894
- tx.delete(adsConnections).where(eq24(adsConnections.id, row.id)).run();
23172
+ tx.delete(adsConnections).where(eq25(adsConnections.id, row.id)).run();
22895
23173
  writeAuditLog(tx, auditFromRequest(request, {
22896
23174
  projectId: project.id,
22897
23175
  actor: "api",
@@ -22907,18 +23185,18 @@ async function adsRoutes(app, opts) {
22907
23185
  });
22908
23186
  app.get("/projects/:name/ads/status", async (request) => {
22909
23187
  const project = resolveProject(app.db, request.params.name);
22910
- const row = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
23188
+ const row = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
22911
23189
  return statusDto(row);
22912
23190
  });
22913
23191
  app.post("/projects/:name/ads/sync", async (request) => {
22914
23192
  const project = resolveProject(app.db, request.params.name);
22915
- const row = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
23193
+ const row = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
22916
23194
  if (!row) {
22917
23195
  throw validationError('No ads connection for this project. Run "canonry ads connect" first.');
22918
23196
  }
22919
- const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and18(
22920
- eq24(runs.projectId, project.id),
22921
- eq24(runs.kind, RunKinds["ads-sync"]),
23197
+ const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and19(
23198
+ eq25(runs.projectId, project.id),
23199
+ eq25(runs.kind, RunKinds["ads-sync"]),
22922
23200
  inArray12(runs.status, [RunStatuses.queued, RunStatuses.running])
22923
23201
  )).get();
22924
23202
  if (inFlight) {
@@ -22940,9 +23218,9 @@ async function adsRoutes(app, opts) {
22940
23218
  });
22941
23219
  app.get("/projects/:name/ads/campaigns", async (request) => {
22942
23220
  const project = resolveProject(app.db, request.params.name);
22943
- const campaignRows = app.db.select().from(adsCampaigns).where(eq24(adsCampaigns.projectId, project.id)).all();
22944
- const groupRows = app.db.select().from(adsAdGroups).where(eq24(adsAdGroups.projectId, project.id)).all();
22945
- const adRows = app.db.select().from(adsAds).where(eq24(adsAds.projectId, project.id)).all();
23221
+ const campaignRows = app.db.select().from(adsCampaigns).where(eq25(adsCampaigns.projectId, project.id)).all();
23222
+ const groupRows = app.db.select().from(adsAdGroups).where(eq25(adsAdGroups.projectId, project.id)).all();
23223
+ const adRows = app.db.select().from(adsAds).where(eq25(adsAds.projectId, project.id)).all();
22946
23224
  const adsByGroup = /* @__PURE__ */ new Map();
22947
23225
  for (const ad of adRows) {
22948
23226
  const dto = {
@@ -22996,12 +23274,12 @@ async function adsRoutes(app, opts) {
22996
23274
  }
22997
23275
  parsedLevel = result.data;
22998
23276
  }
22999
- const conditions = [eq24(adsInsightsDaily.projectId, project.id)];
23000
- if (parsedLevel) conditions.push(eq24(adsInsightsDaily.level, parsedLevel));
23001
- if (entityId) conditions.push(eq24(adsInsightsDaily.entityId, entityId));
23002
- if (from) conditions.push(gte3(adsInsightsDaily.date, from));
23003
- if (to) conditions.push(lte2(adsInsightsDaily.date, to));
23004
- const rows = app.db.select().from(adsInsightsDaily).where(and18(...conditions)).orderBy(asc3(adsInsightsDaily.date)).all();
23277
+ const conditions = [eq25(adsInsightsDaily.projectId, project.id)];
23278
+ if (parsedLevel) conditions.push(eq25(adsInsightsDaily.level, parsedLevel));
23279
+ if (entityId) conditions.push(eq25(adsInsightsDaily.entityId, entityId));
23280
+ if (from) conditions.push(gte4(adsInsightsDaily.date, from));
23281
+ if (to) conditions.push(lte3(adsInsightsDaily.date, to));
23282
+ const rows = app.db.select().from(adsInsightsDaily).where(and19(...conditions)).orderBy(asc4(adsInsightsDaily.date)).all();
23005
23283
  const dtoRows = rows.map((row) => ({
23006
23284
  level: row.level,
23007
23285
  entityId: row.entityId,
@@ -23013,19 +23291,19 @@ async function adsRoutes(app, opts) {
23013
23291
  ctr: adsCtr(row.clicks, row.impressions),
23014
23292
  cpcMicros: adsCpcMicros(row.spendMicros, row.clicks)
23015
23293
  }));
23016
- const conn = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
23294
+ const conn = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
23017
23295
  const response = { rows: dtoRows, currencyCode: conn?.currencyCode ?? null };
23018
23296
  return response;
23019
23297
  });
23020
23298
  app.get("/projects/:name/ads/summary", async (request) => {
23021
23299
  const project = resolveProject(app.db, request.params.name);
23022
- const row = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
23023
- const campaignCount = app.db.select().from(adsCampaigns).where(eq24(adsCampaigns.projectId, project.id)).all().length;
23024
- const adGroupCount = app.db.select().from(adsAdGroups).where(eq24(adsAdGroups.projectId, project.id)).all().length;
23025
- const adCount = app.db.select().from(adsAds).where(eq24(adsAds.projectId, project.id)).all().length;
23026
- const campaignInsights = app.db.select().from(adsInsightsDaily).where(and18(
23027
- eq24(adsInsightsDaily.projectId, project.id),
23028
- eq24(adsInsightsDaily.level, "campaign")
23300
+ const row = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
23301
+ const campaignCount = app.db.select().from(adsCampaigns).where(eq25(adsCampaigns.projectId, project.id)).all().length;
23302
+ const adGroupCount = app.db.select().from(adsAdGroups).where(eq25(adsAdGroups.projectId, project.id)).all().length;
23303
+ const adCount = app.db.select().from(adsAds).where(eq25(adsAds.projectId, project.id)).all().length;
23304
+ const campaignInsights = app.db.select().from(adsInsightsDaily).where(and19(
23305
+ eq25(adsInsightsDaily.projectId, project.id),
23306
+ eq25(adsInsightsDaily.level, "campaign")
23029
23307
  )).all();
23030
23308
  let impressions = 0;
23031
23309
  let clicks = 0;
@@ -23065,7 +23343,7 @@ async function adsRoutes(app, opts) {
23065
23343
 
23066
23344
  // ../api-routes/src/bing.ts
23067
23345
  import crypto21 from "crypto";
23068
- import { eq as eq25, and as and19, desc as desc12 } from "drizzle-orm";
23346
+ import { eq as eq26, and as and20, desc as desc12 } from "drizzle-orm";
23069
23347
 
23070
23348
  // ../integration-bing/src/constants.ts
23071
23349
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
@@ -23439,7 +23717,7 @@ async function bingRoutes(app, opts) {
23439
23717
  const store = requireConnectionStore();
23440
23718
  const project = resolveProject(app.db, request.params.name);
23441
23719
  requireConnection(store, project.canonicalDomain);
23442
- const allInspections = app.db.select().from(bingUrlInspections).where(eq25(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
23720
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq26(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
23443
23721
  const latestByUrl = /* @__PURE__ */ new Map();
23444
23722
  const definitiveByUrl = /* @__PURE__ */ new Map();
23445
23723
  for (const row of allInspections) {
@@ -23528,7 +23806,7 @@ async function bingRoutes(app, opts) {
23528
23806
  const project = resolveProject(app.db, request.params.name);
23529
23807
  const parsed = parseInt(request.query.limit ?? "90", 10);
23530
23808
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
23531
- const rows = app.db.select().from(bingCoverageSnapshots).where(eq25(bingCoverageSnapshots.projectId, project.id)).orderBy(desc12(bingCoverageSnapshots.date)).limit(limit).all();
23809
+ const rows = app.db.select().from(bingCoverageSnapshots).where(eq26(bingCoverageSnapshots.projectId, project.id)).orderBy(desc12(bingCoverageSnapshots.date)).limit(limit).all();
23532
23810
  return rows.map((r) => ({
23533
23811
  date: r.date,
23534
23812
  indexed: r.indexed,
@@ -23540,7 +23818,7 @@ async function bingRoutes(app, opts) {
23540
23818
  requireConnectionStore();
23541
23819
  const project = resolveProject(app.db, request.params.name);
23542
23820
  const { url, limit } = request.query;
23543
- const whereClause = url ? and19(eq25(bingUrlInspections.projectId, project.id), eq25(bingUrlInspections.url, url)) : eq25(bingUrlInspections.projectId, project.id);
23821
+ const whereClause = url ? and20(eq26(bingUrlInspections.projectId, project.id), eq26(bingUrlInspections.url, url)) : eq26(bingUrlInspections.projectId, project.id);
23544
23822
  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();
23545
23823
  return filtered.map((r) => ({
23546
23824
  id: r.id,
@@ -23630,7 +23908,7 @@ async function bingRoutes(app, opts) {
23630
23908
  anchorCount: result.AnchorCount ?? null,
23631
23909
  discoveryDate
23632
23910
  }).run();
23633
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq25(runs.id, runId)).run();
23911
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq26(runs.id, runId)).run();
23634
23912
  return {
23635
23913
  id,
23636
23914
  url,
@@ -23646,7 +23924,7 @@ async function bingRoutes(app, opts) {
23646
23924
  } catch (e) {
23647
23925
  const msg = e instanceof Error ? e.message : String(e);
23648
23926
  bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
23649
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq25(runs.id, runId)).run();
23927
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq26(runs.id, runId)).run();
23650
23928
  throw e;
23651
23929
  }
23652
23930
  });
@@ -23673,7 +23951,7 @@ async function bingRoutes(app, opts) {
23673
23951
  } else {
23674
23952
  bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
23675
23953
  }
23676
- const run = app.db.select().from(runs).where(eq25(runs.id, runId)).get();
23954
+ const run = app.db.select().from(runs).where(eq26(runs.id, runId)).get();
23677
23955
  return run;
23678
23956
  });
23679
23957
  app.post("/projects/:name/bing/request-indexing", async (request) => {
@@ -23685,7 +23963,7 @@ async function bingRoutes(app, opts) {
23685
23963
  }
23686
23964
  let urlsToSubmit = request.body?.urls ?? [];
23687
23965
  if (request.body?.allUnindexed) {
23688
- const allInspections = app.db.select().from(bingUrlInspections).where(eq25(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
23966
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq26(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
23689
23967
  const latestByUrl = /* @__PURE__ */ new Map();
23690
23968
  for (const row of allInspections) {
23691
23969
  if (!latestByUrl.has(row.url)) {
@@ -23772,14 +24050,14 @@ async function bingRoutes(app, opts) {
23772
24050
  import fs from "fs";
23773
24051
  import path from "path";
23774
24052
  import os from "os";
23775
- import { eq as eq26, and as and20 } from "drizzle-orm";
24053
+ import { eq as eq27, and as and21 } from "drizzle-orm";
23776
24054
  function getScreenshotDir() {
23777
24055
  return path.join(os.homedir(), ".canonry", "screenshots");
23778
24056
  }
23779
24057
  async function cdpRoutes(app, opts) {
23780
24058
  app.get("/screenshots/:snapshotId", async (request, reply) => {
23781
24059
  const { snapshotId } = request.params;
23782
- const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath, projectId: runs.projectId }).from(querySnapshots).innerJoin(runs, eq26(querySnapshots.runId, runs.id)).where(eq26(querySnapshots.id, snapshotId)).get();
24060
+ const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath, projectId: runs.projectId }).from(querySnapshots).innerJoin(runs, eq27(querySnapshots.runId, runs.id)).where(eq27(querySnapshots.id, snapshotId)).get();
23783
24061
  if (!snapshot?.screenshotPath) {
23784
24062
  const err = notFound("Screenshot", snapshotId);
23785
24063
  return reply.code(err.statusCode).send(err.toJSON());
@@ -23847,7 +24125,7 @@ async function cdpRoutes(app, opts) {
23847
24125
  async (request, reply) => {
23848
24126
  const project = resolveProject(app.db, request.params.name);
23849
24127
  const { runId } = request.params;
23850
- const run = app.db.select().from(runs).where(and20(eq26(runs.id, runId), eq26(runs.projectId, project.id))).get();
24128
+ const run = app.db.select().from(runs).where(and21(eq27(runs.id, runId), eq27(runs.projectId, project.id))).get();
23851
24129
  if (!run) {
23852
24130
  const err = notFound("Run", runId);
23853
24131
  return reply.code(err.statusCode).send(err.toJSON());
@@ -23860,8 +24138,8 @@ async function cdpRoutes(app, opts) {
23860
24138
  citedDomains: querySnapshots.citedDomains,
23861
24139
  screenshotPath: querySnapshots.screenshotPath,
23862
24140
  rawResponse: querySnapshots.rawResponse
23863
- }).from(querySnapshots).where(eq26(querySnapshots.runId, runId)).all());
23864
- const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq26(queries.projectId, project.id)).all();
24141
+ }).from(querySnapshots).where(eq27(querySnapshots.runId, runId)).all());
24142
+ const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq27(queries.projectId, project.id)).all();
23865
24143
  const queryMap = new Map(queryRows.map((q) => [q.id, q.query]));
23866
24144
  const byQuery = /* @__PURE__ */ new Map();
23867
24145
  for (const snap of snapshots) {
@@ -23944,7 +24222,7 @@ async function cdpRoutes(app, opts) {
23944
24222
 
23945
24223
  // ../api-routes/src/ga.ts
23946
24224
  import crypto22 from "crypto";
23947
- import { eq as eq27, desc as desc13, and as and21, sql as sql10 } from "drizzle-orm";
24225
+ import { eq as eq28, desc as desc13, and as and22, sql as sql10 } from "drizzle-orm";
23948
24226
  function gaLog(level, action, ctx) {
23949
24227
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
23950
24228
  const stream = level === "error" ? process.stderr : process.stdout;
@@ -24221,10 +24499,10 @@ async function ga4Routes(app, opts) {
24221
24499
  if (!saConn && !oauthConn) {
24222
24500
  throw notFound("GA4 connection", project.name);
24223
24501
  }
24224
- app.db.delete(gaTrafficSnapshots).where(eq27(gaTrafficSnapshots.projectId, project.id)).run();
24225
- app.db.delete(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).run();
24226
- app.db.delete(gaAiReferrals).where(eq27(gaAiReferrals.projectId, project.id)).run();
24227
- app.db.delete(gaSocialReferrals).where(eq27(gaSocialReferrals.projectId, project.id)).run();
24502
+ app.db.delete(gaTrafficSnapshots).where(eq28(gaTrafficSnapshots.projectId, project.id)).run();
24503
+ app.db.delete(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).run();
24504
+ app.db.delete(gaAiReferrals).where(eq28(gaAiReferrals.projectId, project.id)).run();
24505
+ app.db.delete(gaSocialReferrals).where(eq28(gaSocialReferrals.projectId, project.id)).run();
24228
24506
  const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
24229
24507
  opts.ga4CredentialStore?.deleteConnection(project.name);
24230
24508
  opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
@@ -24245,7 +24523,7 @@ async function ga4Routes(app, opts) {
24245
24523
  if (!connected) {
24246
24524
  return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
24247
24525
  }
24248
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
24526
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
24249
24527
  return {
24250
24528
  connected: true,
24251
24529
  propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
@@ -24309,8 +24587,8 @@ async function ga4Routes(app, opts) {
24309
24587
  app.db.transaction((tx) => {
24310
24588
  if (syncTraffic) {
24311
24589
  tx.delete(gaTrafficSnapshots).where(
24312
- and21(
24313
- eq27(gaTrafficSnapshots.projectId, project.id),
24590
+ and22(
24591
+ eq28(gaTrafficSnapshots.projectId, project.id),
24314
24592
  sql10`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
24315
24593
  sql10`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
24316
24594
  )
@@ -24333,8 +24611,8 @@ async function ga4Routes(app, opts) {
24333
24611
  }
24334
24612
  if (syncAi) {
24335
24613
  tx.delete(gaAiReferrals).where(
24336
- and21(
24337
- eq27(gaAiReferrals.projectId, project.id),
24614
+ and22(
24615
+ eq28(gaAiReferrals.projectId, project.id),
24338
24616
  sql10`${gaAiReferrals.date} >= ${summary.periodStart}`,
24339
24617
  sql10`${gaAiReferrals.date} <= ${summary.periodEnd}`
24340
24618
  )
@@ -24365,8 +24643,8 @@ async function ga4Routes(app, opts) {
24365
24643
  }
24366
24644
  if (syncSocial) {
24367
24645
  tx.delete(gaSocialReferrals).where(
24368
- and21(
24369
- eq27(gaSocialReferrals.projectId, project.id),
24646
+ and22(
24647
+ eq28(gaSocialReferrals.projectId, project.id),
24370
24648
  sql10`${gaSocialReferrals.date} >= ${summary.periodStart}`,
24371
24649
  sql10`${gaSocialReferrals.date} <= ${summary.periodEnd}`
24372
24650
  )
@@ -24387,7 +24665,7 @@ async function ga4Routes(app, opts) {
24387
24665
  }
24388
24666
  }
24389
24667
  if (syncSummary) {
24390
- tx.delete(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).run();
24668
+ tx.delete(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).run();
24391
24669
  tx.insert(gaTrafficSummaries).values({
24392
24670
  id: crypto22.randomUUID(),
24393
24671
  projectId: project.id,
@@ -24399,7 +24677,7 @@ async function ga4Routes(app, opts) {
24399
24677
  syncedAt: now,
24400
24678
  syncRunId: runId
24401
24679
  }).run();
24402
- tx.delete(gaTrafficWindowSummaries).where(eq27(gaTrafficWindowSummaries.projectId, project.id)).run();
24680
+ tx.delete(gaTrafficWindowSummaries).where(eq28(gaTrafficWindowSummaries.projectId, project.id)).run();
24403
24681
  for (const ws of windowSummaries) {
24404
24682
  tx.insert(gaTrafficWindowSummaries).values({
24405
24683
  id: crypto22.randomUUID(),
@@ -24417,7 +24695,7 @@ async function ga4Routes(app, opts) {
24417
24695
  }
24418
24696
  }
24419
24697
  });
24420
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq27(runs.id, runId)).run();
24698
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq28(runs.id, runId)).run();
24421
24699
  const syncedComponents = only ? [
24422
24700
  ...syncTraffic ? ["traffic"] : [],
24423
24701
  ...syncSummary ? ["summary"] : [],
@@ -24446,7 +24724,7 @@ async function ga4Routes(app, opts) {
24446
24724
  } catch (e) {
24447
24725
  const msg = e instanceof Error ? e.message : String(e);
24448
24726
  gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
24449
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq27(runs.id, runId)).run();
24727
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq28(runs.id, runId)).run();
24450
24728
  throw e;
24451
24729
  }
24452
24730
  });
@@ -24457,11 +24735,11 @@ async function ga4Routes(app, opts) {
24457
24735
  const window = parseWindow(request.query.window);
24458
24736
  const cutoff = windowCutoff(window);
24459
24737
  const cutoffDate = cutoff?.slice(0, 10) ?? null;
24460
- const snapshotConditions = [eq27(gaTrafficSnapshots.projectId, project.id)];
24738
+ const snapshotConditions = [eq28(gaTrafficSnapshots.projectId, project.id)];
24461
24739
  if (cutoffDate) snapshotConditions.push(sql10`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
24462
- const aiConditions = [eq27(gaAiReferrals.projectId, project.id)];
24740
+ const aiConditions = [eq28(gaAiReferrals.projectId, project.id)];
24463
24741
  if (cutoffDate) aiConditions.push(sql10`${gaAiReferrals.date} >= ${cutoffDate}`);
24464
- const socialConditions = [eq27(gaSocialReferrals.projectId, project.id)];
24742
+ const socialConditions = [eq28(gaSocialReferrals.projectId, project.id)];
24465
24743
  if (cutoffDate) socialConditions.push(sql10`${gaSocialReferrals.date} >= ${cutoffDate}`);
24466
24744
  const windowSummaryRow = cutoffDate ? app.db.select({
24467
24745
  totalSessions: gaTrafficWindowSummaries.totalSessions,
@@ -24469,35 +24747,35 @@ async function ga4Routes(app, opts) {
24469
24747
  totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
24470
24748
  totalUsers: gaTrafficWindowSummaries.totalUsers
24471
24749
  }).from(gaTrafficWindowSummaries).where(
24472
- and21(
24473
- eq27(gaTrafficWindowSummaries.projectId, project.id),
24474
- eq27(gaTrafficWindowSummaries.windowKey, window)
24750
+ and22(
24751
+ eq28(gaTrafficWindowSummaries.projectId, project.id),
24752
+ eq28(gaTrafficWindowSummaries.windowKey, window)
24475
24753
  )
24476
24754
  ).get() : null;
24477
24755
  const snapshotTotalsRow = cutoffDate && !windowSummaryRow ? app.db.select({
24478
24756
  totalSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
24479
24757
  totalOrganicSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
24480
24758
  totalUsers: sql10`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
24481
- }).from(gaTrafficSnapshots).where(and21(...snapshotConditions)).get() : null;
24759
+ }).from(gaTrafficSnapshots).where(and22(...snapshotConditions)).get() : null;
24482
24760
  const summaryRow = cutoffDate ? windowSummaryRow ?? snapshotTotalsRow : app.db.select({
24483
24761
  totalSessions: gaTrafficSummaries.totalSessions,
24484
24762
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
24485
24763
  totalUsers: gaTrafficSummaries.totalUsers
24486
- }).from(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).get();
24764
+ }).from(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).get();
24487
24765
  const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
24488
24766
  totalDirectSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
24489
- }).from(gaTrafficSnapshots).where(and21(...snapshotConditions)).get();
24767
+ }).from(gaTrafficSnapshots).where(and22(...snapshotConditions)).get();
24490
24768
  const summaryMeta = app.db.select({
24491
24769
  periodStart: gaTrafficSummaries.periodStart,
24492
24770
  periodEnd: gaTrafficSummaries.periodEnd
24493
- }).from(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).get();
24771
+ }).from(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).get();
24494
24772
  const rows = app.db.select({
24495
24773
  landingPage: sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
24496
24774
  sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
24497
24775
  organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
24498
24776
  directSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
24499
24777
  users: sql10`SUM(${gaTrafficSnapshots.users})`
24500
- }).from(gaTrafficSnapshots).where(and21(...snapshotConditions)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
24778
+ }).from(gaTrafficSnapshots).where(and22(...snapshotConditions)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
24501
24779
  const aiReferralRows = app.db.select({
24502
24780
  source: gaAiReferrals.source,
24503
24781
  medium: gaAiReferrals.medium,
@@ -24505,7 +24783,7 @@ async function ga4Routes(app, opts) {
24505
24783
  sourceDimension: gaAiReferrals.sourceDimension,
24506
24784
  sessions: sql10`SUM(${gaAiReferrals.sessions})`,
24507
24785
  users: sql10`SUM(${gaAiReferrals.users})`
24508
- }).from(gaAiReferrals).where(and21(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.trafficClass, gaAiReferrals.sourceDimension).all();
24786
+ }).from(gaAiReferrals).where(and22(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.trafficClass, gaAiReferrals.sourceDimension).all();
24509
24787
  const aiReferralLandingPageRows = app.db.select({
24510
24788
  source: gaAiReferrals.source,
24511
24789
  medium: gaAiReferrals.medium,
@@ -24514,7 +24792,7 @@ async function ga4Routes(app, opts) {
24514
24792
  landingPage: sql10`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
24515
24793
  sessions: sql10`SUM(${gaAiReferrals.sessions})`,
24516
24794
  users: sql10`SUM(${gaAiReferrals.users})`
24517
- }).from(gaAiReferrals).where(and21(...aiConditions)).groupBy(
24795
+ }).from(gaAiReferrals).where(and22(...aiConditions)).groupBy(
24518
24796
  gaAiReferrals.source,
24519
24797
  gaAiReferrals.medium,
24520
24798
  gaAiReferrals.trafficClass,
@@ -24530,7 +24808,7 @@ async function ga4Routes(app, opts) {
24530
24808
  channelGroup: gaAiReferrals.channelGroup,
24531
24809
  sessions: gaAiReferrals.sessions,
24532
24810
  users: gaAiReferrals.users
24533
- }).from(gaAiReferrals).where(and21(...aiConditions)).all();
24811
+ }).from(gaAiReferrals).where(and22(...aiConditions)).all();
24534
24812
  const aiReferrals = pickWinningDimension(
24535
24813
  aiReferralRows,
24536
24814
  (r) => `${r.source}\0${r.medium}\0${r.trafficClass}`
@@ -24546,12 +24824,12 @@ async function ga4Routes(app, opts) {
24546
24824
  channelGroup: gaSocialReferrals.channelGroup,
24547
24825
  sessions: sql10`SUM(${gaSocialReferrals.sessions})`,
24548
24826
  users: sql10`SUM(${gaSocialReferrals.users})`
24549
- }).from(gaSocialReferrals).where(and21(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql10`SUM(${gaSocialReferrals.sessions}) DESC`).all();
24827
+ }).from(gaSocialReferrals).where(and22(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql10`SUM(${gaSocialReferrals.sessions}) DESC`).all();
24550
24828
  const socialTotals = app.db.select({
24551
24829
  sessions: sql10`SUM(${gaSocialReferrals.sessions})`,
24552
24830
  users: sql10`SUM(${gaSocialReferrals.users})`
24553
- }).from(gaSocialReferrals).where(and21(...socialConditions)).get();
24554
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
24831
+ }).from(gaSocialReferrals).where(and22(...socialConditions)).get();
24832
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
24555
24833
  const total = summaryRow?.totalSessions ?? 0;
24556
24834
  const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
24557
24835
  const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
@@ -24649,7 +24927,7 @@ async function ga4Routes(app, opts) {
24649
24927
  const project = resolveProject(app.db, request.params.name);
24650
24928
  requireGa4Connection(opts, project.name, project.canonicalDomain);
24651
24929
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
24652
- const conditions = [eq27(gaAiReferrals.projectId, project.id)];
24930
+ const conditions = [eq28(gaAiReferrals.projectId, project.id)];
24653
24931
  if (cutoffDate) conditions.push(sql10`${gaAiReferrals.date} >= ${cutoffDate}`);
24654
24932
  const rows = app.db.select({
24655
24933
  date: gaAiReferrals.date,
@@ -24660,7 +24938,7 @@ async function ga4Routes(app, opts) {
24660
24938
  sourceDimension: gaAiReferrals.sourceDimension,
24661
24939
  sessions: sql10`SUM(${gaAiReferrals.sessions})`,
24662
24940
  users: sql10`SUM(${gaAiReferrals.users})`
24663
- }).from(gaAiReferrals).where(and21(...conditions)).groupBy(
24941
+ }).from(gaAiReferrals).where(and22(...conditions)).groupBy(
24664
24942
  gaAiReferrals.date,
24665
24943
  gaAiReferrals.source,
24666
24944
  gaAiReferrals.medium,
@@ -24674,7 +24952,7 @@ async function ga4Routes(app, opts) {
24674
24952
  const project = resolveProject(app.db, request.params.name);
24675
24953
  requireGa4Connection(opts, project.name, project.canonicalDomain);
24676
24954
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
24677
- const conditions = [eq27(gaSocialReferrals.projectId, project.id)];
24955
+ const conditions = [eq28(gaSocialReferrals.projectId, project.id)];
24678
24956
  if (cutoffDate) conditions.push(sql10`${gaSocialReferrals.date} >= ${cutoffDate}`);
24679
24957
  const rows = app.db.select({
24680
24958
  date: gaSocialReferrals.date,
@@ -24683,7 +24961,7 @@ async function ga4Routes(app, opts) {
24683
24961
  channelGroup: gaSocialReferrals.channelGroup,
24684
24962
  sessions: gaSocialReferrals.sessions,
24685
24963
  users: gaSocialReferrals.users
24686
- }).from(gaSocialReferrals).where(and21(...conditions)).orderBy(gaSocialReferrals.date).all();
24964
+ }).from(gaSocialReferrals).where(and22(...conditions)).orderBy(gaSocialReferrals.date).all();
24687
24965
  return rows;
24688
24966
  });
24689
24967
  app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
@@ -24696,8 +24974,8 @@ async function ga4Routes(app, opts) {
24696
24974
  d.setDate(d.getDate() - n);
24697
24975
  return fmt(d);
24698
24976
  };
24699
- const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and21(
24700
- eq27(gaSocialReferrals.projectId, project.id),
24977
+ const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and22(
24978
+ eq28(gaSocialReferrals.projectId, project.id),
24701
24979
  sql10`${gaSocialReferrals.date} >= ${from}`,
24702
24980
  sql10`${gaSocialReferrals.date} < ${to}`
24703
24981
  )).get();
@@ -24709,16 +24987,16 @@ async function ga4Routes(app, opts) {
24709
24987
  const sourceCurrent = app.db.select({
24710
24988
  source: gaSocialReferrals.source,
24711
24989
  sessions: sql10`SUM(${gaSocialReferrals.sessions})`
24712
- }).from(gaSocialReferrals).where(and21(
24713
- eq27(gaSocialReferrals.projectId, project.id),
24990
+ }).from(gaSocialReferrals).where(and22(
24991
+ eq28(gaSocialReferrals.projectId, project.id),
24714
24992
  sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
24715
24993
  sql10`${gaSocialReferrals.date} < ${fmt(today)}`
24716
24994
  )).groupBy(gaSocialReferrals.source).all();
24717
24995
  const sourcePrev = app.db.select({
24718
24996
  source: gaSocialReferrals.source,
24719
24997
  sessions: sql10`SUM(${gaSocialReferrals.sessions})`
24720
- }).from(gaSocialReferrals).where(and21(
24721
- eq27(gaSocialReferrals.projectId, project.id),
24998
+ }).from(gaSocialReferrals).where(and22(
24999
+ eq28(gaSocialReferrals.projectId, project.id),
24722
25000
  sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
24723
25001
  sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`
24724
25002
  )).groupBy(gaSocialReferrals.source).all();
@@ -24759,16 +25037,16 @@ async function ga4Routes(app, opts) {
24759
25037
  return fmt(d);
24760
25038
  };
24761
25039
  const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
24762
- const sumTotal = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq27(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
24763
- const sumOrganic = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq27(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
24764
- const sumDirect = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq27(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
24765
- const sumAi = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and21(
24766
- eq27(gaAiReferrals.projectId, project.id),
25040
+ const sumTotal = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and22(eq28(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
25041
+ const sumOrganic = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and22(eq28(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
25042
+ const sumDirect = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and22(eq28(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
25043
+ const sumAi = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and22(
25044
+ eq28(gaAiReferrals.projectId, project.id),
24767
25045
  sql10`${gaAiReferrals.date} >= ${from}`,
24768
25046
  sql10`${gaAiReferrals.date} < ${to}`,
24769
- eq27(gaAiReferrals.sourceDimension, "session")
25047
+ eq28(gaAiReferrals.sourceDimension, "session")
24770
25048
  )).get();
24771
- const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and21(eq27(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${from}`, sql10`${gaSocialReferrals.date} < ${to}`)).get();
25049
+ const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and22(eq28(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${from}`, sql10`${gaSocialReferrals.date} < ${to}`)).get();
24772
25050
  const todayStr = fmt(today);
24773
25051
  const buildTrend = (sum) => {
24774
25052
  const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
@@ -24777,17 +25055,17 @@ async function ga4Routes(app, opts) {
24777
25055
  const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
24778
25056
  return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
24779
25057
  };
24780
- const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and21(
24781
- eq27(gaAiReferrals.projectId, project.id),
25058
+ const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and22(
25059
+ eq28(gaAiReferrals.projectId, project.id),
24782
25060
  sql10`${gaAiReferrals.date} >= ${daysAgo(7)}`,
24783
25061
  sql10`${gaAiReferrals.date} < ${todayStr}`,
24784
- eq27(gaAiReferrals.sourceDimension, "session")
25062
+ eq28(gaAiReferrals.sourceDimension, "session")
24785
25063
  )).groupBy(gaAiReferrals.source).all();
24786
- const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and21(
24787
- eq27(gaAiReferrals.projectId, project.id),
25064
+ const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and22(
25065
+ eq28(gaAiReferrals.projectId, project.id),
24788
25066
  sql10`${gaAiReferrals.date} >= ${daysAgo(14)}`,
24789
25067
  sql10`${gaAiReferrals.date} < ${daysAgo(7)}`,
24790
- eq27(gaAiReferrals.sourceDimension, "session")
25068
+ eq28(gaAiReferrals.sourceDimension, "session")
24791
25069
  )).groupBy(gaAiReferrals.source).all();
24792
25070
  const findBiggestMover = (current, prev) => {
24793
25071
  const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
@@ -24803,8 +25081,8 @@ async function ga4Routes(app, opts) {
24803
25081
  }
24804
25082
  return mover;
24805
25083
  };
24806
- const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and21(eq27(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql10`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
24807
- const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and21(eq27(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
25084
+ const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and22(eq28(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql10`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
25085
+ const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and22(eq28(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
24808
25086
  return {
24809
25087
  total: buildTrend(sumTotal),
24810
25088
  organic: buildTrend(sumOrganic),
@@ -24819,14 +25097,14 @@ async function ga4Routes(app, opts) {
24819
25097
  const project = resolveProject(app.db, request.params.name);
24820
25098
  requireGa4Connection(opts, project.name, project.canonicalDomain);
24821
25099
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
24822
- const conditions = [eq27(gaTrafficSnapshots.projectId, project.id)];
25100
+ const conditions = [eq28(gaTrafficSnapshots.projectId, project.id)];
24823
25101
  if (cutoffDate) conditions.push(sql10`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
24824
25102
  const rows = app.db.select({
24825
25103
  date: gaTrafficSnapshots.date,
24826
25104
  sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
24827
25105
  organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
24828
25106
  users: sql10`SUM(${gaTrafficSnapshots.users})`
24829
- }).from(gaTrafficSnapshots).where(and21(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
25107
+ }).from(gaTrafficSnapshots).where(and22(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
24830
25108
  return rows.map((r) => ({
24831
25109
  date: r.date,
24832
25110
  sessions: r.sessions ?? 0,
@@ -24842,7 +25120,7 @@ async function ga4Routes(app, opts) {
24842
25120
  sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
24843
25121
  organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
24844
25122
  users: sql10`SUM(${gaTrafficSnapshots.users})`
24845
- }).from(gaTrafficSnapshots).where(eq27(gaTrafficSnapshots.projectId, project.id)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
25123
+ }).from(gaTrafficSnapshots).where(eq28(gaTrafficSnapshots.projectId, project.id)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
24846
25124
  return {
24847
25125
  pages: trafficPages.map((r) => ({
24848
25126
  landingPage: r.landingPage,
@@ -26490,7 +26768,7 @@ async function wordpressRoutes(app, opts) {
26490
26768
 
26491
26769
  // ../api-routes/src/backlinks.ts
26492
26770
  import crypto24 from "crypto";
26493
- import { and as and23, asc as asc4, desc as desc14, eq as eq28, sql as sql11 } from "drizzle-orm";
26771
+ import { and as and24, asc as asc5, desc as desc14, eq as eq29, sql as sql11 } from "drizzle-orm";
26494
26772
 
26495
26773
  // ../integration-commoncrawl/src/constants.ts
26496
26774
  import os2 from "os";
@@ -26893,7 +27171,7 @@ function pruneCachedRelease(release, opts = {}) {
26893
27171
  }
26894
27172
 
26895
27173
  // ../api-routes/src/backlinks-filter.ts
26896
- import { and as and22, ne as ne3, notLike } from "drizzle-orm";
27174
+ import { and as and23, ne as ne3, notLike } from "drizzle-orm";
26897
27175
  var BACKLINK_FILTER_PATTERNS = [
26898
27176
  "*.google.com",
26899
27177
  "*.googleusercontent.com",
@@ -26916,7 +27194,7 @@ function backlinkCrawlerExclusionClause() {
26916
27194
  conditions.push(ne3(backlinkDomains.linkingDomain, pattern));
26917
27195
  }
26918
27196
  }
26919
- const combined = and22(...conditions);
27197
+ const combined = and23(...conditions);
26920
27198
  if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
26921
27199
  return combined;
26922
27200
  }
@@ -26987,11 +27265,11 @@ function mapRunRow(row) {
26987
27265
  }
26988
27266
  function latestSummaryForProject(db, projectId, source, release) {
26989
27267
  const conditions = [
26990
- eq28(backlinkSummaries.projectId, projectId),
26991
- eq28(backlinkSummaries.source, source)
27268
+ eq29(backlinkSummaries.projectId, projectId),
27269
+ eq29(backlinkSummaries.source, source)
26992
27270
  ];
26993
- if (release) conditions.push(eq28(backlinkSummaries.release, release));
26994
- return db.select().from(backlinkSummaries).where(and23(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
27271
+ if (release) conditions.push(eq29(backlinkSummaries.release, release));
27272
+ return db.select().from(backlinkSummaries).where(and24(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
26995
27273
  }
26996
27274
  function parseExcludeCrawlers(value) {
26997
27275
  if (!value) return false;
@@ -26999,12 +27277,12 @@ function parseExcludeCrawlers(value) {
26999
27277
  return lower === "1" || lower === "true" || lower === "yes";
27000
27278
  }
27001
27279
  function computeFilteredSummary(db, base) {
27002
- const baseDomainCondition = and23(
27003
- eq28(backlinkDomains.projectId, base.projectId),
27004
- eq28(backlinkDomains.source, base.source),
27005
- eq28(backlinkDomains.release, base.release)
27280
+ const baseDomainCondition = and24(
27281
+ eq29(backlinkDomains.projectId, base.projectId),
27282
+ eq29(backlinkDomains.source, base.source),
27283
+ eq29(backlinkDomains.release, base.release)
27006
27284
  );
27007
- const filteredCondition = and23(baseDomainCondition, backlinkCrawlerExclusionClause());
27285
+ const filteredCondition = and24(baseDomainCondition, backlinkCrawlerExclusionClause());
27008
27286
  const unfilteredAgg = db.select({
27009
27287
  count: sql11`count(*)`,
27010
27288
  total: sql11`coalesce(sum(${backlinkDomains.numHosts}), 0)`
@@ -27034,13 +27312,13 @@ function computeFilteredSummary(db, base) {
27034
27312
  };
27035
27313
  }
27036
27314
  function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
27037
- const summary = db.select().from(backlinkSummaries).where(and23(eq28(backlinkSummaries.projectId, projectId), eq28(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
27315
+ const summary = db.select().from(backlinkSummaries).where(and24(eq29(backlinkSummaries.projectId, projectId), eq29(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
27038
27316
  let totalLinkingDomains = summary?.totalLinkingDomains ?? 0;
27039
27317
  if (summary && excludeCrawlers) {
27040
- const filtered = db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(and23(
27041
- eq28(backlinkDomains.projectId, projectId),
27042
- eq28(backlinkDomains.source, source),
27043
- eq28(backlinkDomains.release, summary.release),
27318
+ const filtered = db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(and24(
27319
+ eq29(backlinkDomains.projectId, projectId),
27320
+ eq29(backlinkDomains.source, source),
27321
+ eq29(backlinkDomains.release, summary.release),
27044
27322
  backlinkCrawlerExclusionClause()
27045
27323
  )).get();
27046
27324
  totalLinkingDomains = Number(filtered?.count ?? 0);
@@ -27055,7 +27333,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
27055
27333
  };
27056
27334
  }
27057
27335
  function computeSourceAvailability(db, project, bingStore, excludeCrawlers) {
27058
- const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
27336
+ const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq29(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
27059
27337
  const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
27060
27338
  const bingConnected = !!bingStore?.getConnection(project.canonicalDomain);
27061
27339
  const sources = [
@@ -27111,7 +27389,7 @@ async function backlinksRoutes(app, opts) {
27111
27389
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
27112
27390
  );
27113
27391
  }
27114
- const existing = app.db.select().from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.release, release)).get();
27392
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq29(ccReleaseSyncs.release, release)).get();
27115
27393
  const now = (/* @__PURE__ */ new Date()).toISOString();
27116
27394
  if (existing) {
27117
27395
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -27122,9 +27400,9 @@ async function backlinksRoutes(app, opts) {
27122
27400
  phaseDetail: null,
27123
27401
  error: null,
27124
27402
  updatedAt: now
27125
- }).where(eq28(ccReleaseSyncs.id, existing.id)).run();
27403
+ }).where(eq29(ccReleaseSyncs.id, existing.id)).run();
27126
27404
  opts.onReleaseSyncRequested(existing.id, release);
27127
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.id, existing.id)).get();
27405
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq29(ccReleaseSyncs.id, existing.id)).get();
27128
27406
  return reply.status(200).send(mapSyncRow(refreshed));
27129
27407
  }
27130
27408
  const id = crypto24.randomUUID();
@@ -27136,7 +27414,7 @@ async function backlinksRoutes(app, opts) {
27136
27414
  updatedAt: now
27137
27415
  }).run();
27138
27416
  opts.onReleaseSyncRequested(id, release);
27139
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.id, id)).get();
27417
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq29(ccReleaseSyncs.id, id)).get();
27140
27418
  return reply.status(201).send(mapSyncRow(inserted));
27141
27419
  });
27142
27420
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
@@ -27194,7 +27472,7 @@ async function backlinksRoutes(app, opts) {
27194
27472
  createdAt: now
27195
27473
  }).run();
27196
27474
  opts.onBacklinkExtractRequested(runId, project.id, release);
27197
- const run = app.db.select().from(runs).where(eq28(runs.id, runId)).get();
27475
+ const run = app.db.select().from(runs).where(eq29(runs.id, runId)).get();
27198
27476
  return reply.status(201).send(mapRunRow(run));
27199
27477
  });
27200
27478
  app.get(
@@ -27220,12 +27498,12 @@ async function backlinksRoutes(app, opts) {
27220
27498
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
27221
27499
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
27222
27500
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
27223
- const baseDomainCondition = and23(
27224
- eq28(backlinkDomains.projectId, project.id),
27225
- eq28(backlinkDomains.source, source),
27226
- eq28(backlinkDomains.release, targetRelease)
27501
+ const baseDomainCondition = and24(
27502
+ eq29(backlinkDomains.projectId, project.id),
27503
+ eq29(backlinkDomains.source, source),
27504
+ eq29(backlinkDomains.release, targetRelease)
27227
27505
  );
27228
- const domainCondition = excludeCrawlers ? and23(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
27506
+ const domainCondition = excludeCrawlers ? and24(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
27229
27507
  const totalRow = app.db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(domainCondition).get();
27230
27508
  const rows = app.db.select({
27231
27509
  linkingDomain: backlinkDomains.linkingDomain,
@@ -27249,7 +27527,7 @@ async function backlinksRoutes(app, opts) {
27249
27527
  async (request, reply) => {
27250
27528
  const project = resolveProject(app.db, request.params.name);
27251
27529
  const source = parseSourceParam(request.query.source);
27252
- const rows = app.db.select().from(backlinkSummaries).where(and23(eq28(backlinkSummaries.projectId, project.id), eq28(backlinkSummaries.source, source))).orderBy(asc4(backlinkSummaries.queriedAt)).all();
27530
+ const rows = app.db.select().from(backlinkSummaries).where(and24(eq29(backlinkSummaries.projectId, project.id), eq29(backlinkSummaries.source, source))).orderBy(asc5(backlinkSummaries.queriedAt)).all();
27253
27531
  const response = rows.map((r) => ({
27254
27532
  release: r.release,
27255
27533
  totalLinkingDomains: r.totalLinkingDomains,
@@ -27296,7 +27574,7 @@ async function backlinksRoutes(app, opts) {
27296
27574
  createdAt: now
27297
27575
  }).run();
27298
27576
  opts.onBingBacklinkSyncRequested(runId, project.id);
27299
- const run = app.db.select().from(runs).where(eq28(runs.id, runId)).get();
27577
+ const run = app.db.select().from(runs).where(eq29(runs.id, runId)).get();
27300
27578
  return reply.status(201).send(mapRunRow(run));
27301
27579
  }
27302
27580
  );
@@ -27305,7 +27583,7 @@ async function backlinksRoutes(app, opts) {
27305
27583
  // ../api-routes/src/traffic.ts
27306
27584
  import crypto26 from "crypto";
27307
27585
  import { Agent as UndiciAgent } from "undici";
27308
- import { and as and24, desc as desc15, eq as eq29, gte as gte4, lte as lte3, sql as sql12 } from "drizzle-orm";
27586
+ import { and as and25, desc as desc15, eq as eq30, gte as gte5, lte as lte4, sql as sql12 } from "drizzle-orm";
27309
27587
 
27310
27588
  // ../integration-cloud-run/src/auth.ts
27311
27589
  import crypto25 from "crypto";
@@ -31172,8 +31450,8 @@ async function runBackfillTask(options) {
31172
31450
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
31173
31451
  try {
31174
31452
  app.db.transaction((tx) => {
31175
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq29(runs.id, runId)).run();
31176
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq29(trafficSources.id, sourceRow.id)).run();
31453
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq30(runs.id, runId)).run();
31454
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq30(trafficSources.id, sourceRow.id)).run();
31177
31455
  });
31178
31456
  } catch {
31179
31457
  }
@@ -31188,7 +31466,7 @@ async function runBackfillTask(options) {
31188
31466
  if (allEvents.length === 0) {
31189
31467
  const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
31190
31468
  try {
31191
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq29(runs.id, runId)).run();
31469
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq30(runs.id, runId)).run();
31192
31470
  } catch {
31193
31471
  }
31194
31472
  return;
@@ -31210,31 +31488,31 @@ async function runBackfillTask(options) {
31210
31488
  try {
31211
31489
  app.db.transaction((tx) => {
31212
31490
  tx.delete(crawlerEventsHourly).where(
31213
- and24(
31214
- eq29(crawlerEventsHourly.sourceId, sourceRow.id),
31215
- gte4(crawlerEventsHourly.tsHour, windowStartIso),
31216
- lte3(crawlerEventsHourly.tsHour, windowEndIso)
31491
+ and25(
31492
+ eq30(crawlerEventsHourly.sourceId, sourceRow.id),
31493
+ gte5(crawlerEventsHourly.tsHour, windowStartIso),
31494
+ lte4(crawlerEventsHourly.tsHour, windowEndIso)
31217
31495
  )
31218
31496
  ).run();
31219
31497
  tx.delete(aiUserFetchEventsHourly).where(
31220
- and24(
31221
- eq29(aiUserFetchEventsHourly.sourceId, sourceRow.id),
31222
- gte4(aiUserFetchEventsHourly.tsHour, windowStartIso),
31223
- lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
31498
+ and25(
31499
+ eq30(aiUserFetchEventsHourly.sourceId, sourceRow.id),
31500
+ gte5(aiUserFetchEventsHourly.tsHour, windowStartIso),
31501
+ lte4(aiUserFetchEventsHourly.tsHour, windowEndIso)
31224
31502
  )
31225
31503
  ).run();
31226
31504
  tx.delete(aiReferralEventsHourly).where(
31227
- and24(
31228
- eq29(aiReferralEventsHourly.sourceId, sourceRow.id),
31229
- gte4(aiReferralEventsHourly.tsHour, windowStartIso),
31230
- lte3(aiReferralEventsHourly.tsHour, windowEndIso)
31505
+ and25(
31506
+ eq30(aiReferralEventsHourly.sourceId, sourceRow.id),
31507
+ gte5(aiReferralEventsHourly.tsHour, windowStartIso),
31508
+ lte4(aiReferralEventsHourly.tsHour, windowEndIso)
31231
31509
  )
31232
31510
  ).run();
31233
31511
  tx.delete(rawEventSamples).where(
31234
- and24(
31235
- eq29(rawEventSamples.sourceId, sourceRow.id),
31236
- gte4(rawEventSamples.ts, windowStartIso),
31237
- lte3(rawEventSamples.ts, windowEndIso)
31512
+ and25(
31513
+ eq30(rawEventSamples.sourceId, sourceRow.id),
31514
+ gte5(rawEventSamples.ts, windowStartIso),
31515
+ lte4(rawEventSamples.ts, windowEndIso)
31238
31516
  )
31239
31517
  ).run();
31240
31518
  for (const bucket of report.crawlerEventsHourly) {
@@ -31323,8 +31601,8 @@ async function runBackfillTask(options) {
31323
31601
  lastError: null,
31324
31602
  lastEventIds: newRingBuffer,
31325
31603
  updatedAt: finishedAt
31326
- }).where(eq29(trafficSources.id, sourceRow.id)).run();
31327
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq29(runs.id, runId)).run();
31604
+ }).where(eq30(trafficSources.id, sourceRow.id)).run();
31605
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq30(runs.id, runId)).run();
31328
31606
  });
31329
31607
  } catch (e) {
31330
31608
  markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -31406,7 +31684,7 @@ async function trafficRoutes(app, opts) {
31406
31684
  createdAt: existing?.createdAt ?? now,
31407
31685
  updatedAt: now
31408
31686
  });
31409
- const activeSource = app.db.select().from(trafficSources).where(eq29(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
31687
+ const activeSource = app.db.select().from(trafficSources).where(eq30(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
31410
31688
  const config = {
31411
31689
  gcpProjectId,
31412
31690
  serviceName: serviceName ?? null,
@@ -31422,8 +31700,8 @@ async function trafficRoutes(app, opts) {
31422
31700
  lastError: null,
31423
31701
  configJson: config,
31424
31702
  updatedAt: now
31425
- }).where(eq29(trafficSources.id, activeSource.id)).run();
31426
- sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, activeSource.id)).get();
31703
+ }).where(eq30(trafficSources.id, activeSource.id)).run();
31704
+ sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, activeSource.id)).get();
31427
31705
  } else {
31428
31706
  const newId = crypto26.randomUUID();
31429
31707
  app.db.insert(trafficSources).values({
@@ -31440,7 +31718,7 @@ async function trafficRoutes(app, opts) {
31440
31718
  createdAt: now,
31441
31719
  updatedAt: now
31442
31720
  }).run();
31443
- sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, newId)).get();
31721
+ sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, newId)).get();
31444
31722
  }
31445
31723
  writeAuditLog(app.db, {
31446
31724
  projectId: project.id,
@@ -31492,7 +31770,7 @@ async function trafficRoutes(app, opts) {
31492
31770
  createdAt: existing?.createdAt ?? now,
31493
31771
  updatedAt: now
31494
31772
  });
31495
- const activeSource = app.db.select().from(trafficSources).where(eq29(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
31773
+ const activeSource = app.db.select().from(trafficSources).where(eq30(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
31496
31774
  const config = { baseUrl, username };
31497
31775
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
31498
31776
  let sourceRow;
@@ -31503,8 +31781,8 @@ async function trafficRoutes(app, opts) {
31503
31781
  lastError: null,
31504
31782
  configJson: config,
31505
31783
  updatedAt: now
31506
- }).where(eq29(trafficSources.id, activeSource.id)).run();
31507
- sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, activeSource.id)).get();
31784
+ }).where(eq30(trafficSources.id, activeSource.id)).run();
31785
+ sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, activeSource.id)).get();
31508
31786
  } else {
31509
31787
  const newId = crypto26.randomUUID();
31510
31788
  app.db.insert(trafficSources).values({
@@ -31521,7 +31799,7 @@ async function trafficRoutes(app, opts) {
31521
31799
  createdAt: now,
31522
31800
  updatedAt: now
31523
31801
  }).run();
31524
- sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, newId)).get();
31802
+ sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, newId)).get();
31525
31803
  }
31526
31804
  writeAuditLog(app.db, {
31527
31805
  projectId: project.id,
@@ -31575,7 +31853,7 @@ async function trafficRoutes(app, opts) {
31575
31853
  createdAt: existing?.createdAt ?? now,
31576
31854
  updatedAt: now
31577
31855
  });
31578
- const activeSource = app.db.select().from(trafficSources).where(eq29(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
31856
+ const activeSource = app.db.select().from(trafficSources).where(eq30(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
31579
31857
  const config = { projectId, teamId, environment };
31580
31858
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
31581
31859
  const { sourceRow, scheduleCreated } = app.db.transaction((tx) => {
@@ -31587,8 +31865,8 @@ async function trafficRoutes(app, opts) {
31587
31865
  lastError: null,
31588
31866
  configJson: config,
31589
31867
  updatedAt: now
31590
- }).where(eq29(trafficSources.id, activeSource.id)).run();
31591
- row = tx.select().from(trafficSources).where(eq29(trafficSources.id, activeSource.id)).get();
31868
+ }).where(eq30(trafficSources.id, activeSource.id)).run();
31869
+ row = tx.select().from(trafficSources).where(eq30(trafficSources.id, activeSource.id)).get();
31592
31870
  } else {
31593
31871
  const newId = crypto26.randomUUID();
31594
31872
  tx.insert(trafficSources).values({
@@ -31613,12 +31891,12 @@ async function trafficRoutes(app, opts) {
31613
31891
  createdAt: now,
31614
31892
  updatedAt: now
31615
31893
  }).run();
31616
- row = tx.select().from(trafficSources).where(eq29(trafficSources.id, newId)).get();
31894
+ row = tx.select().from(trafficSources).where(eq30(trafficSources.id, newId)).get();
31617
31895
  }
31618
31896
  const existingSchedule = tx.select().from(schedules).where(
31619
- and24(
31620
- eq29(schedules.projectId, project.id),
31621
- eq29(schedules.kind, SchedulableRunKinds["traffic-sync"])
31897
+ and25(
31898
+ eq30(schedules.projectId, project.id),
31899
+ eq30(schedules.kind, SchedulableRunKinds["traffic-sync"])
31622
31900
  )
31623
31901
  ).get();
31624
31902
  let created = false;
@@ -31667,7 +31945,7 @@ async function trafficRoutes(app, opts) {
31667
31945
  });
31668
31946
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
31669
31947
  const project = resolveProject(app.db, request.params.name);
31670
- const sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, request.params.id)).get();
31948
+ const sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, request.params.id)).get();
31671
31949
  if (!sourceRow || sourceRow.projectId !== project.id) {
31672
31950
  throw notFound("Traffic source", request.params.id);
31673
31951
  }
@@ -31693,8 +31971,8 @@ async function trafficRoutes(app, opts) {
31693
31971
  const markFailed = (msg, errorCode) => {
31694
31972
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
31695
31973
  app.db.transaction((tx) => {
31696
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq29(runs.id, runId)).run();
31697
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq29(trafficSources.id, sourceRow.id)).run();
31974
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq30(runs.id, runId)).run();
31975
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq30(trafficSources.id, sourceRow.id)).run();
31698
31976
  });
31699
31977
  try {
31700
31978
  opts.onTrafficSynced?.({
@@ -31775,7 +32053,7 @@ async function trafficRoutes(app, opts) {
31775
32053
  }
31776
32054
  const credential = credentialStore.getConnection(project.name);
31777
32055
  if (!credential) {
31778
- app.db.delete(runs).where(eq29(runs.id, runId)).run();
32056
+ app.db.delete(runs).where(eq30(runs.id, runId)).run();
31779
32057
  throw validationError(
31780
32058
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
31781
32059
  );
@@ -31824,12 +32102,12 @@ async function trafficRoutes(app, opts) {
31824
32102
  auditAction = "traffic.vercel.synced";
31825
32103
  const credentialStore = opts.vercelTrafficCredentialStore;
31826
32104
  if (!credentialStore) {
31827
- app.db.delete(runs).where(eq29(runs.id, runId)).run();
32105
+ app.db.delete(runs).where(eq30(runs.id, runId)).run();
31828
32106
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
31829
32107
  }
31830
32108
  const credential = credentialStore.getConnection(project.name);
31831
32109
  if (!credential) {
31832
- app.db.delete(runs).where(eq29(runs.id, runId)).run();
32110
+ app.db.delete(runs).where(eq30(runs.id, runId)).run();
31833
32111
  throw validationError(
31834
32112
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
31835
32113
  );
@@ -31929,7 +32207,7 @@ async function trafficRoutes(app, opts) {
31929
32207
  let aiReferralHitsCount = 0;
31930
32208
  let unknownHitsCount = 0;
31931
32209
  app.db.transaction((tx) => {
31932
- const latestRow = tx.select().from(trafficSources).where(eq29(trafficSources.id, sourceRow.id)).get();
32210
+ const latestRow = tx.select().from(trafficSources).where(eq30(trafficSources.id, sourceRow.id)).get();
31933
32211
  const previousIds = latestRow.lastEventIds ?? [];
31934
32212
  const seenEventIds = new Set(previousIds);
31935
32213
  const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
@@ -32104,8 +32382,8 @@ async function trafficRoutes(app, opts) {
32104
32382
  if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
32105
32383
  sourceUpdate.lastCursor = nextCursor ?? null;
32106
32384
  }
32107
- tx.update(trafficSources).set(sourceUpdate).where(eq29(trafficSources.id, sourceRow.id)).run();
32108
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq29(runs.id, runId)).run();
32385
+ tx.update(trafficSources).set(sourceUpdate).where(eq30(trafficSources.id, sourceRow.id)).run();
32386
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq30(runs.id, runId)).run();
32109
32387
  });
32110
32388
  writeAuditLog(app.db, {
32111
32389
  projectId: project.id,
@@ -32157,7 +32435,7 @@ async function trafficRoutes(app, opts) {
32157
32435
  });
32158
32436
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
32159
32437
  const project = resolveProject(app.db, request.params.name);
32160
- const sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, request.params.id)).get();
32438
+ const sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, request.params.id)).get();
32161
32439
  if (!sourceRow || sourceRow.projectId !== project.id) {
32162
32440
  throw notFound("Traffic source", request.params.id);
32163
32441
  }
@@ -32345,9 +32623,9 @@ async function trafficRoutes(app, opts) {
32345
32623
  pathNormalized: crawlerEventsHourly.pathNormalized,
32346
32624
  hits: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
32347
32625
  }).from(crawlerEventsHourly).where(
32348
- and24(
32349
- eq29(crawlerEventsHourly.sourceId, row.id),
32350
- gte4(crawlerEventsHourly.tsHour, since)
32626
+ and25(
32627
+ eq30(crawlerEventsHourly.sourceId, row.id),
32628
+ gte5(crawlerEventsHourly.tsHour, since)
32351
32629
  )
32352
32630
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
32353
32631
  const crawlerSegments = segmentCrawlerHits(
@@ -32355,28 +32633,28 @@ async function trafficRoutes(app, opts) {
32355
32633
  );
32356
32634
  const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
32357
32635
  const aiUserFetchTotals = app.db.select({ total: sql12`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
32358
- and24(
32359
- eq29(aiUserFetchEventsHourly.sourceId, row.id),
32360
- gte4(aiUserFetchEventsHourly.tsHour, since)
32636
+ and25(
32637
+ eq30(aiUserFetchEventsHourly.sourceId, row.id),
32638
+ gte5(aiUserFetchEventsHourly.tsHour, since)
32361
32639
  )
32362
32640
  ).get();
32363
32641
  const aiTotals = app.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
32364
- and24(
32365
- eq29(aiReferralEventsHourly.sourceId, row.id),
32366
- gte4(aiReferralEventsHourly.tsHour, since)
32642
+ and25(
32643
+ eq30(aiReferralEventsHourly.sourceId, row.id),
32644
+ gte5(aiReferralEventsHourly.tsHour, since)
32367
32645
  )
32368
32646
  ).get();
32369
32647
  const sampleTotals = app.db.select({ total: sql12`COUNT(*)` }).from(rawEventSamples).where(
32370
- and24(
32371
- eq29(rawEventSamples.sourceId, row.id),
32372
- gte4(rawEventSamples.ts, since)
32648
+ and25(
32649
+ eq30(rawEventSamples.sourceId, row.id),
32650
+ gte5(rawEventSamples.ts, since)
32373
32651
  )
32374
32652
  ).get();
32375
32653
  const latestRun = app.db.select().from(runs).where(
32376
- and24(
32377
- eq29(runs.projectId, projectId),
32378
- eq29(runs.kind, RunKinds["traffic-sync"]),
32379
- eq29(runs.sourceId, row.id)
32654
+ and25(
32655
+ eq30(runs.projectId, projectId),
32656
+ eq30(runs.kind, RunKinds["traffic-sync"]),
32657
+ eq30(runs.sourceId, row.id)
32380
32658
  )
32381
32659
  ).orderBy(desc15(runs.startedAt)).limit(1).get();
32382
32660
  return {
@@ -32407,7 +32685,7 @@ async function trafficRoutes(app, opts) {
32407
32685
  "`advanceToNow` must be `true`. There is no implicit reset."
32408
32686
  );
32409
32687
  }
32410
- const sourceRow = app.db.select().from(trafficSources).where(and24(eq29(trafficSources.projectId, project.id), eq29(trafficSources.id, request.params.id))).get();
32688
+ const sourceRow = app.db.select().from(trafficSources).where(and25(eq30(trafficSources.projectId, project.id), eq30(trafficSources.id, request.params.id))).get();
32411
32689
  if (!sourceRow) {
32412
32690
  throw notFound("traffic source", request.params.id);
32413
32691
  }
@@ -32424,7 +32702,7 @@ async function trafficRoutes(app, opts) {
32424
32702
  status: TrafficSourceStatuses.connected,
32425
32703
  lastError: null,
32426
32704
  updatedAt: now
32427
- }).where(eq29(trafficSources.id, sourceRow.id)).run();
32705
+ }).where(eq30(trafficSources.id, sourceRow.id)).run();
32428
32706
  writeAuditLog(tx, auditFromRequest(request, {
32429
32707
  projectId: project.id,
32430
32708
  actor: "api",
@@ -32432,20 +32710,20 @@ async function trafficRoutes(app, opts) {
32432
32710
  entityType: "traffic_source",
32433
32711
  entityId: sourceRow.id
32434
32712
  }));
32435
- updatedRow = tx.select().from(trafficSources).where(eq29(trafficSources.id, sourceRow.id)).get();
32713
+ updatedRow = tx.select().from(trafficSources).where(eq30(trafficSources.id, sourceRow.id)).get();
32436
32714
  });
32437
32715
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
32438
32716
  });
32439
32717
  app.get("/projects/:name/traffic/sources", async (request) => {
32440
32718
  const project = resolveProject(app.db, request.params.name);
32441
- const rows = app.db.select().from(trafficSources).where(eq29(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
32719
+ const rows = app.db.select().from(trafficSources).where(eq30(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
32442
32720
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
32443
32721
  const response = { sources };
32444
32722
  return response;
32445
32723
  });
32446
32724
  app.get("/projects/:name/traffic/status", async (request) => {
32447
32725
  const project = resolveProject(app.db, request.params.name);
32448
- const rows = app.db.select().from(trafficSources).where(eq29(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
32726
+ const rows = app.db.select().from(trafficSources).where(eq30(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
32449
32727
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
32450
32728
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
32451
32729
  const response = { sources };
@@ -32455,7 +32733,7 @@ async function trafficRoutes(app, opts) {
32455
32733
  "/projects/:name/traffic/sources/:id",
32456
32734
  async (request) => {
32457
32735
  const project = resolveProject(app.db, request.params.name);
32458
- const row = app.db.select().from(trafficSources).where(eq29(trafficSources.id, request.params.id)).get();
32736
+ const row = app.db.select().from(trafficSources).where(eq30(trafficSources.id, request.params.id)).get();
32459
32737
  if (!row || row.projectId !== project.id) {
32460
32738
  throw notFound("Traffic source", request.params.id);
32461
32739
  }
@@ -32507,12 +32785,12 @@ async function trafficRoutes(app, opts) {
32507
32785
  let aiReferralCounts = aiReferralClassCounts(0, 0, 0);
32508
32786
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
32509
32787
  const crawlerFilters = [
32510
- eq29(crawlerEventsHourly.projectId, project.id),
32511
- gte4(crawlerEventsHourly.tsHour, sinceIso),
32512
- lte3(crawlerEventsHourly.tsHour, untilIso)
32788
+ eq30(crawlerEventsHourly.projectId, project.id),
32789
+ gte5(crawlerEventsHourly.tsHour, sinceIso),
32790
+ lte4(crawlerEventsHourly.tsHour, untilIso)
32513
32791
  ];
32514
- if (sourceIdParam) crawlerFilters.push(eq29(crawlerEventsHourly.sourceId, sourceIdParam));
32515
- const crawlerWhere = and24(...crawlerFilters);
32792
+ if (sourceIdParam) crawlerFilters.push(eq30(crawlerEventsHourly.sourceId, sourceIdParam));
32793
+ const crawlerWhere = and25(...crawlerFilters);
32516
32794
  const pathTotals = app.db.select({
32517
32795
  pathNormalized: crawlerEventsHourly.pathNormalized,
32518
32796
  hits: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
@@ -32539,12 +32817,12 @@ async function trafficRoutes(app, opts) {
32539
32817
  }
32540
32818
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
32541
32819
  const userFetchFilters = [
32542
- eq29(aiUserFetchEventsHourly.projectId, project.id),
32543
- gte4(aiUserFetchEventsHourly.tsHour, sinceIso),
32544
- lte3(aiUserFetchEventsHourly.tsHour, untilIso)
32820
+ eq30(aiUserFetchEventsHourly.projectId, project.id),
32821
+ gte5(aiUserFetchEventsHourly.tsHour, sinceIso),
32822
+ lte4(aiUserFetchEventsHourly.tsHour, untilIso)
32545
32823
  ];
32546
- if (sourceIdParam) userFetchFilters.push(eq29(aiUserFetchEventsHourly.sourceId, sourceIdParam));
32547
- const userFetchWhere = and24(...userFetchFilters);
32824
+ if (sourceIdParam) userFetchFilters.push(eq30(aiUserFetchEventsHourly.sourceId, sourceIdParam));
32825
+ const userFetchWhere = and25(...userFetchFilters);
32548
32826
  const total = app.db.select({ total: sql12`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
32549
32827
  aiUserFetchTotal = Number(total?.total ?? 0);
32550
32828
  const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc15(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
@@ -32564,12 +32842,12 @@ async function trafficRoutes(app, opts) {
32564
32842
  }
32565
32843
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
32566
32844
  const aiFilters = [
32567
- eq29(aiReferralEventsHourly.projectId, project.id),
32568
- gte4(aiReferralEventsHourly.tsHour, sinceIso),
32569
- lte3(aiReferralEventsHourly.tsHour, untilIso)
32845
+ eq30(aiReferralEventsHourly.projectId, project.id),
32846
+ gte5(aiReferralEventsHourly.tsHour, sinceIso),
32847
+ lte4(aiReferralEventsHourly.tsHour, untilIso)
32570
32848
  ];
32571
- if (sourceIdParam) aiFilters.push(eq29(aiReferralEventsHourly.sourceId, sourceIdParam));
32572
- const aiWhere = and24(...aiFilters);
32849
+ if (sourceIdParam) aiFilters.push(eq30(aiReferralEventsHourly.sourceId, sourceIdParam));
32850
+ const aiWhere = and25(...aiFilters);
32573
32851
  const total = app.db.select({
32574
32852
  total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
32575
32853
  paid: sql12`COALESCE(SUM(${aiReferralEventsHourly.paidSessionsOrHits}), 0)`,
@@ -32791,7 +33069,7 @@ function readInstalledManifest(skillDir) {
32791
33069
  var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck];
32792
33070
 
32793
33071
  // ../api-routes/src/doctor/checks/backlinks.ts
32794
- import { and as and25, eq as eq30 } from "drizzle-orm";
33072
+ import { and as and26, eq as eq31 } from "drizzle-orm";
32795
33073
  function skippedNoProject() {
32796
33074
  return {
32797
33075
  status: CheckStatuses.skipped,
@@ -32807,8 +33085,8 @@ var BACKLINKS_CHECKS = [
32807
33085
  title: "Backlinks source connected",
32808
33086
  run: (ctx) => {
32809
33087
  if (!ctx.project) return skippedNoProject();
32810
- const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq30(projects.id, ctx.project.id)).get();
32811
- const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq30(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
33088
+ const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq31(projects.id, ctx.project.id)).get();
33089
+ const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq31(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
32812
33090
  const ccConnected = projectRow?.autoExtract === true && !!readySync;
32813
33091
  const bingConnected = !!ctx.bingConnectionStore?.getConnection(ctx.project.canonicalDomain);
32814
33092
  const connected = [];
@@ -32823,9 +33101,9 @@ var BACKLINKS_CHECKS = [
32823
33101
  details: { commoncrawl: ccConnected, bingWebmaster: bingConnected }
32824
33102
  };
32825
33103
  }
32826
- const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and25(
32827
- eq30(backlinkSummaries.projectId, ctx.project.id),
32828
- eq30(backlinkSummaries.source, BacklinkSources.commoncrawl)
33104
+ const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and26(
33105
+ eq31(backlinkSummaries.projectId, ctx.project.id),
33106
+ eq31(backlinkSummaries.source, BacklinkSources.commoncrawl)
32829
33107
  )).limit(1).get() : false;
32830
33108
  return {
32831
33109
  status: CheckStatuses.ok,
@@ -32985,7 +33263,7 @@ var BING_AUTH_CHECKS = [
32985
33263
  ];
32986
33264
 
32987
33265
  // ../api-routes/src/doctor/checks/content.ts
32988
- import { eq as eq31 } from "drizzle-orm";
33266
+ import { eq as eq32 } from "drizzle-orm";
32989
33267
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
32990
33268
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
32991
33269
  function skippedNoProject2() {
@@ -32998,7 +33276,7 @@ function skippedNoProject2() {
32998
33276
  }
32999
33277
  function loadProject(ctx) {
33000
33278
  if (!ctx.project) return null;
33001
- return ctx.db.select().from(projects).where(eq31(projects.id, ctx.project.id)).get() ?? null;
33279
+ return ctx.db.select().from(projects).where(eq32(projects.id, ctx.project.id)).get() ?? null;
33002
33280
  }
33003
33281
  function percent(value) {
33004
33282
  return Math.round(value * 100);
@@ -33090,7 +33368,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
33090
33368
  );
33091
33369
 
33092
33370
  // ../api-routes/src/doctor/checks/ads.ts
33093
- import { eq as eq32 } from "drizzle-orm";
33371
+ import { eq as eq33 } from "drizzle-orm";
33094
33372
  var RECENT_SYNC_WARN_DAYS = 7;
33095
33373
  var RECENT_SYNC_FAIL_DAYS = 30;
33096
33374
  var adsConnectionCheck = {
@@ -33107,7 +33385,7 @@ var adsConnectionCheck = {
33107
33385
  remediation: null
33108
33386
  };
33109
33387
  }
33110
- const row = ctx.db.select().from(adsConnections).where(eq32(adsConnections.projectId, ctx.project.id)).get();
33388
+ const row = ctx.db.select().from(adsConnections).where(eq33(adsConnections.projectId, ctx.project.id)).get();
33111
33389
  if (!row) {
33112
33390
  return {
33113
33391
  status: CheckStatuses.skipped,
@@ -33157,7 +33435,7 @@ var adsRecentSyncCheck = {
33157
33435
  remediation: null
33158
33436
  };
33159
33437
  }
33160
- const row = ctx.db.select().from(adsConnections).where(eq32(adsConnections.projectId, ctx.project.id)).get();
33438
+ const row = ctx.db.select().from(adsConnections).where(eq33(adsConnections.projectId, ctx.project.id)).get();
33161
33439
  if (!row) {
33162
33440
  return {
33163
33441
  status: CheckStatuses.skipped,
@@ -33348,7 +33626,7 @@ var ga4ConnectionCheck = {
33348
33626
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
33349
33627
 
33350
33628
  // ../api-routes/src/doctor/checks/gbp-auth.ts
33351
- import { and as and26, eq as eq33 } from "drizzle-orm";
33629
+ import { and as and27, eq as eq34 } from "drizzle-orm";
33352
33630
  var RECENT_SYNC_WARN_DAYS2 = 7;
33353
33631
  var RECENT_SYNC_FAIL_DAYS2 = 30;
33354
33632
  function skippedNoProject3() {
@@ -33581,7 +33859,7 @@ var recentSyncCheck = {
33581
33859
  title: "GBP recent sync",
33582
33860
  run: (ctx) => {
33583
33861
  if (!ctx.project) return skippedNoProject3();
33584
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and26(eq33(gbpLocations.projectId, ctx.project.id), eq33(gbpLocations.selected, true))).all();
33862
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and27(eq34(gbpLocations.projectId, ctx.project.id), eq34(gbpLocations.selected, true))).all();
33585
33863
  if (selected.length === 0) {
33586
33864
  return {
33587
33865
  status: CheckStatuses.skipped,
@@ -33641,7 +33919,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
33641
33919
  );
33642
33920
 
33643
33921
  // ../api-routes/src/doctor/checks/places.ts
33644
- import { eq as eq34 } from "drizzle-orm";
33922
+ import { eq as eq35 } from "drizzle-orm";
33645
33923
  var apiKeyCheck = {
33646
33924
  id: "gbp.places.api-key",
33647
33925
  category: CheckCategories.auth,
@@ -33686,7 +33964,7 @@ var apiKeyCheck = {
33686
33964
  details: { tier: cfg.tier }
33687
33965
  };
33688
33966
  }
33689
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq34(gbpLocations.projectId, ctx.project.id)).all();
33967
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq35(gbpLocations.projectId, ctx.project.id)).all();
33690
33968
  const selected = rows.filter((r) => r.selected);
33691
33969
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
33692
33970
  const details = {
@@ -34183,7 +34461,7 @@ var RUNTIME_STATE_CHECKS = [
34183
34461
  ];
34184
34462
 
34185
34463
  // ../api-routes/src/doctor/checks/traffic-source.ts
34186
- import { and as and27, eq as eq35, gte as gte5, ne as ne4, sql as sql13 } from "drizzle-orm";
34464
+ import { and as and28, eq as eq36, gte as gte6, ne as ne4, sql as sql13 } from "drizzle-orm";
34187
34465
  var RECENT_DATA_WARN_DAYS = 7;
34188
34466
  var RECENT_DATA_FAIL_DAYS = 30;
34189
34467
  function skippedNoProject5() {
@@ -34197,8 +34475,8 @@ function skippedNoProject5() {
34197
34475
  function loadProbes(ctx) {
34198
34476
  if (!ctx.project) return [];
34199
34477
  const rows = ctx.db.select().from(trafficSources).where(
34200
- and27(
34201
- eq35(trafficSources.projectId, ctx.project.id),
34478
+ and28(
34479
+ eq36(trafficSources.projectId, ctx.project.id),
34202
34480
  ne4(trafficSources.status, TrafficSourceStatuses.archived)
34203
34481
  )
34204
34482
  ).all();
@@ -34278,17 +34556,17 @@ var recentDataCheck = {
34278
34556
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
34279
34557
  const recentCrawlers = Number(
34280
34558
  ctx.db.select({ total: sql13`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
34281
- and27(
34282
- eq35(crawlerEventsHourly.projectId, ctx.project.id),
34283
- gte5(crawlerEventsHourly.tsHour, warnCutoff)
34559
+ and28(
34560
+ eq36(crawlerEventsHourly.projectId, ctx.project.id),
34561
+ gte6(crawlerEventsHourly.tsHour, warnCutoff)
34284
34562
  )
34285
34563
  ).get()?.total ?? 0
34286
34564
  );
34287
34565
  const recentReferrals = Number(
34288
34566
  ctx.db.select({ total: sql13`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
34289
- and27(
34290
- eq35(aiReferralEventsHourly.projectId, ctx.project.id),
34291
- gte5(aiReferralEventsHourly.tsHour, warnCutoff)
34567
+ and28(
34568
+ eq36(aiReferralEventsHourly.projectId, ctx.project.id),
34569
+ gte6(aiReferralEventsHourly.tsHour, warnCutoff)
34292
34570
  )
34293
34571
  ).get()?.total ?? 0
34294
34572
  );
@@ -34302,17 +34580,17 @@ var recentDataCheck = {
34302
34580
  }
34303
34581
  const olderCrawlers = Number(
34304
34582
  ctx.db.select({ total: sql13`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
34305
- and27(
34306
- eq35(crawlerEventsHourly.projectId, ctx.project.id),
34307
- gte5(crawlerEventsHourly.tsHour, failCutoff)
34583
+ and28(
34584
+ eq36(crawlerEventsHourly.projectId, ctx.project.id),
34585
+ gte6(crawlerEventsHourly.tsHour, failCutoff)
34308
34586
  )
34309
34587
  ).get()?.total ?? 0
34310
34588
  );
34311
34589
  const olderReferrals = Number(
34312
34590
  ctx.db.select({ total: sql13`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
34313
- and27(
34314
- eq35(aiReferralEventsHourly.projectId, ctx.project.id),
34315
- gte5(aiReferralEventsHourly.tsHour, failCutoff)
34591
+ and28(
34592
+ eq36(aiReferralEventsHourly.projectId, ctx.project.id),
34593
+ gte6(aiReferralEventsHourly.tsHour, failCutoff)
34316
34594
  )
34317
34595
  ).get()?.total ?? 0
34318
34596
  );
@@ -34727,7 +35005,7 @@ async function doctorRoutes(app, opts) {
34727
35005
 
34728
35006
  // ../api-routes/src/discovery/routes.ts
34729
35007
  import crypto28 from "crypto";
34730
- import { and as and28, desc as desc16, eq as eq36, gte as gte6, inArray as inArray13, isNull, or as or5 } from "drizzle-orm";
35008
+ import { and as and29, desc as desc16, eq as eq37, gte as gte7, inArray as inArray13, isNull, or as or5 } from "drizzle-orm";
34731
35009
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
34732
35010
  async function discoveryRoutes(app, opts) {
34733
35011
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -34761,13 +35039,13 @@ async function discoveryRoutes(app, opts) {
34761
35039
  const now = (/* @__PURE__ */ new Date()).toISOString();
34762
35040
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
34763
35041
  const decision = app.db.transaction((tx) => {
34764
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and28(
34765
- eq36(discoverySessions.projectId, project.id),
34766
- eq36(discoverySessions.icpDescription, icpDescription),
35042
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and29(
35043
+ eq37(discoverySessions.projectId, project.id),
35044
+ eq37(discoverySessions.icpDescription, icpDescription),
34767
35045
  // Buyer is part of session identity: it changes the seed prompt's
34768
35046
  // semantics, so a request with a different (or no) buyer must start
34769
35047
  // its own session, never adopt another buyer's probes.
34770
- parsed.data.buyerDescription == null ? isNull(discoverySessions.buyerDescription) : eq36(discoverySessions.buyerDescription, parsed.data.buyerDescription),
35048
+ parsed.data.buyerDescription == null ? isNull(discoverySessions.buyerDescription) : eq37(discoverySessions.buyerDescription, parsed.data.buyerDescription),
34771
35049
  // Locations are identity too: a different service-area subset seeds
34772
35050
  // and probes a different geo, so it must never reuse another geo's
34773
35051
  // session. resolveLocations is deterministic (project-config order),
@@ -34777,17 +35055,17 @@ async function discoveryRoutes(app, opts) {
34777
35055
  // locations a NULL row's subset is unknowable, so it conservatively
34778
35056
  // never reuses — a one-time, bounded (2h window) non-reuse after
34779
35057
  // upgrade, never a wrong reuse.
34780
- locations.length === 0 ? or5(isNull(discoverySessions.locations), eq36(discoverySessions.locations, locations)) : eq36(discoverySessions.locations, locations),
35058
+ locations.length === 0 ? or5(isNull(discoverySessions.locations), eq37(discoverySessions.locations, locations)) : eq37(discoverySessions.locations, locations),
34781
35059
  // Seed provider set is identity: a different phrasing distribution
34782
35060
  // must never reuse another set's session. Null (= Gemini-only
34783
35061
  // default, including explicit ['gemini']) matches legacy rows.
34784
- seedProviders == null ? isNull(discoverySessions.seedProviders) : eq36(discoverySessions.seedProviders, seedProviders),
35062
+ seedProviders == null ? isNull(discoverySessions.seedProviders) : eq37(discoverySessions.seedProviders, seedProviders),
34785
35063
  inArray13(discoverySessions.status, [
34786
35064
  DiscoverySessionStatuses.queued,
34787
35065
  DiscoverySessionStatuses.seeding,
34788
35066
  DiscoverySessionStatuses.probing
34789
35067
  ]),
34790
- gte6(discoverySessions.createdAt, ageFloorIso)
35068
+ gte7(discoverySessions.createdAt, ageFloorIso)
34791
35069
  )).orderBy(desc16(discoverySessions.createdAt)).get();
34792
35070
  if (existing && existing.runId) {
34793
35071
  return { reused: true, sessionId: existing.id, runId: existing.runId };
@@ -34857,7 +35135,7 @@ async function discoveryRoutes(app, opts) {
34857
35135
  const project = resolveProject(app.db, request.params.name);
34858
35136
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
34859
35137
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
34860
- const rows = app.db.select().from(discoverySessions).where(eq36(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
35138
+ const rows = app.db.select().from(discoverySessions).where(eq37(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
34861
35139
  return reply.send(rows.map(serializeSession));
34862
35140
  }
34863
35141
  );
@@ -34865,11 +35143,11 @@ async function discoveryRoutes(app, opts) {
34865
35143
  "/projects/:name/discover/sessions/:id",
34866
35144
  async (request, reply) => {
34867
35145
  const project = resolveProject(app.db, request.params.name);
34868
- const session = app.db.select().from(discoverySessions).where(eq36(discoverySessions.id, request.params.id)).get();
35146
+ const session = app.db.select().from(discoverySessions).where(eq37(discoverySessions.id, request.params.id)).get();
34869
35147
  if (!session || session.projectId !== project.id) {
34870
35148
  throw notFound("Discovery session", request.params.id);
34871
35149
  }
34872
- const probeRows = app.db.select().from(discoveryProbes).where(eq36(discoveryProbes.sessionId, session.id)).all();
35150
+ const probeRows = app.db.select().from(discoveryProbes).where(eq37(discoveryProbes.sessionId, session.id)).all();
34873
35151
  const detail = {
34874
35152
  ...serializeSession(session),
34875
35153
  probes: probeRows.map(serializeProbe)
@@ -34881,7 +35159,7 @@ async function discoveryRoutes(app, opts) {
34881
35159
  "/projects/:name/discover/sessions/:id/harvest",
34882
35160
  async (request, reply) => {
34883
35161
  const project = resolveProject(app.db, request.params.name);
34884
- const session = app.db.select().from(discoverySessions).where(eq36(discoverySessions.id, request.params.id)).get();
35162
+ const session = app.db.select().from(discoverySessions).where(eq37(discoverySessions.id, request.params.id)).get();
34885
35163
  if (!session || session.projectId !== project.id) {
34886
35164
  throw notFound("Discovery session", request.params.id);
34887
35165
  }
@@ -34889,7 +35167,7 @@ async function discoveryRoutes(app, opts) {
34889
35167
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
34890
35168
  const applyAnchor = request.query.anchor !== "false";
34891
35169
  const provider = session.seedProvider ?? "gemini";
34892
- const probeRows = app.db.select().from(discoveryProbes).where(eq36(discoveryProbes.sessionId, session.id)).all();
35170
+ const probeRows = app.db.select().from(discoveryProbes).where(eq37(discoveryProbes.sessionId, session.id)).all();
34893
35171
  const extract = opts.harvestSearchQueries;
34894
35172
  const probesWithQueries = probeRows.map((row) => {
34895
35173
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -34900,7 +35178,7 @@ async function discoveryRoutes(app, opts) {
34900
35178
  return { searchQueries: [] };
34901
35179
  }
34902
35180
  });
34903
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq36(queries.projectId, project.id)).all().map((r) => r.query);
35181
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq37(queries.projectId, project.id)).all().map((r) => r.query);
34904
35182
  const anchorTerms = buildHarvestAnchorTerms(
34905
35183
  [session.icpDescription ?? "", ...trackedQueries],
34906
35184
  effectiveDomains(project)
@@ -34947,12 +35225,12 @@ async function discoveryRoutes(app, opts) {
34947
35225
  "/projects/:name/discover/sessions/:id/promote",
34948
35226
  async (request, reply) => {
34949
35227
  const project = resolveProject(app.db, request.params.name);
34950
- const session = app.db.select().from(discoverySessions).where(eq36(discoverySessions.id, request.params.id)).get();
35228
+ const session = app.db.select().from(discoverySessions).where(eq37(discoverySessions.id, request.params.id)).get();
34951
35229
  if (!session || session.projectId !== project.id) {
34952
35230
  throw notFound("Discovery session", request.params.id);
34953
35231
  }
34954
- const probeRows = app.db.select().from(discoveryProbes).where(eq36(discoveryProbes.sessionId, session.id)).all();
34955
- const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq36(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
35232
+ const probeRows = app.db.select().from(discoveryProbes).where(eq37(discoveryProbes.sessionId, session.id)).all();
35233
+ const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq37(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
34956
35234
  const seenCompetitors = new Set(existingCompetitors);
34957
35235
  const cited = /* @__PURE__ */ new Set();
34958
35236
  const aspirational = /* @__PURE__ */ new Set();
@@ -34981,7 +35259,7 @@ async function discoveryRoutes(app, opts) {
34981
35259
  );
34982
35260
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
34983
35261
  const project = resolveProject(app.db, request.params.name);
34984
- const session = app.db.select().from(discoverySessions).where(eq36(discoverySessions.id, request.params.id)).get();
35262
+ const session = app.db.select().from(discoverySessions).where(eq37(discoverySessions.id, request.params.id)).get();
34985
35263
  if (!session || session.projectId !== project.id) {
34986
35264
  throw notFound("Discovery session", request.params.id);
34987
35265
  }
@@ -35004,7 +35282,7 @@ async function discoveryRoutes(app, opts) {
35004
35282
  const bucketSet = new Set(buckets);
35005
35283
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
35006
35284
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
35007
- const probeRows = app.db.select().from(discoveryProbes).where(eq36(discoveryProbes.sessionId, session.id)).all();
35285
+ const probeRows = app.db.select().from(discoveryProbes).where(eq37(discoveryProbes.sessionId, session.id)).all();
35008
35286
  const candidateQueries = /* @__PURE__ */ new Set();
35009
35287
  for (const probe of probeRows) {
35010
35288
  if (!probe.bucket) continue;
@@ -35012,7 +35290,7 @@ async function discoveryRoutes(app, opts) {
35012
35290
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
35013
35291
  }
35014
35292
  const existingQueries = new Set(
35015
- app.db.select({ query: queries.query }).from(queries).where(eq36(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
35293
+ app.db.select({ query: queries.query }).from(queries).where(eq37(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
35016
35294
  );
35017
35295
  const promotedQueries = [];
35018
35296
  const skippedQueries = [];
@@ -35028,7 +35306,7 @@ async function discoveryRoutes(app, opts) {
35028
35306
  const skippedCompetitors = [];
35029
35307
  if (includeCompetitors) {
35030
35308
  const existingCompetitors = new Set(
35031
- app.db.select({ domain: competitors.domain }).from(competitors).where(eq36(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
35309
+ app.db.select({ domain: competitors.domain }).from(competitors).where(eq37(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
35032
35310
  );
35033
35311
  const competitorMap = parseCompetitorMap(session.competitorMap);
35034
35312
  for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
@@ -35147,7 +35425,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
35147
35425
 
35148
35426
  // ../api-routes/src/discovery/orchestrate.ts
35149
35427
  import crypto29 from "crypto";
35150
- import { eq as eq37 } from "drizzle-orm";
35428
+ import { eq as eq38 } from "drizzle-orm";
35151
35429
  var DEFAULT_MAX_PROBES = 100;
35152
35430
  var ABSOLUTE_MAX_PROBES = 500;
35153
35431
  function classifyProbeBucket(input) {
@@ -35234,7 +35512,7 @@ async function executeDiscovery(opts) {
35234
35512
  status: DiscoverySessionStatuses.seeding,
35235
35513
  dedupThreshold,
35236
35514
  startedAt
35237
- }).where(eq37(discoverySessions.id, opts.sessionId)).run();
35515
+ }).where(eq38(discoverySessions.id, opts.sessionId)).run();
35238
35516
  const seedResult = await opts.deps.seed({
35239
35517
  project: opts.project,
35240
35518
  icpDescription: opts.icpDescription,
@@ -35295,7 +35573,7 @@ async function executeDiscovery(opts) {
35295
35573
  dedupBandPairFraction: dedupStats.bandPairFraction,
35296
35574
  dedupPairsTotal: dedupStats.pairsTotal,
35297
35575
  warning
35298
- }).where(eq37(discoverySessions.id, opts.sessionId)).run();
35576
+ }).where(eq38(discoverySessions.id, opts.sessionId)).run();
35299
35577
  const probeLocation = opts.locations?.[0];
35300
35578
  const probeResults = await mapWithConcurrency(
35301
35579
  probedCanonicals,
@@ -35349,7 +35627,7 @@ async function executeDiscovery(opts) {
35349
35627
  wastedCount: buckets["wasted-surface"],
35350
35628
  competitorMap,
35351
35629
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
35352
- }).where(eq37(discoverySessions.id, opts.sessionId)).run();
35630
+ }).where(eq38(discoverySessions.id, opts.sessionId)).run();
35353
35631
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
35354
35632
  return {
35355
35633
  buckets,
@@ -35389,7 +35667,7 @@ function markSessionFailed(db, sessionId, error) {
35389
35667
  status: DiscoverySessionStatuses.failed,
35390
35668
  error,
35391
35669
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
35392
- }).where(eq37(discoverySessions.id, sessionId)).run();
35670
+ }).where(eq38(discoverySessions.id, sessionId)).run();
35393
35671
  }
35394
35672
  function dedupeStrings(input) {
35395
35673
  const seen = /* @__PURE__ */ new Set();
@@ -35407,7 +35685,7 @@ function dedupeStrings(input) {
35407
35685
 
35408
35686
  // ../api-routes/src/technical-aeo.ts
35409
35687
  import crypto30 from "crypto";
35410
- import { and as and29, asc as asc5, count, desc as desc17, eq as eq38, inArray as inArray14 } from "drizzle-orm";
35688
+ import { and as and30, asc as asc6, count, desc as desc17, eq as eq39, inArray as inArray14 } from "drizzle-orm";
35411
35689
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
35412
35690
  function emptyScore(projectName) {
35413
35691
  return {
@@ -35439,9 +35717,9 @@ function parsePositiveInt(value, fallback, max) {
35439
35717
  async function technicalAeoRoutes(app, opts) {
35440
35718
  app.get("/projects/:name/technical-aeo", async (request) => {
35441
35719
  const project = resolveProject(app.db, request.params.name);
35442
- const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq38(siteAuditSnapshots.runId, runs.id)).where(and29(
35443
- eq38(siteAuditSnapshots.projectId, project.id),
35444
- eq38(runs.kind, RunKinds["site-audit"]),
35720
+ const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq39(siteAuditSnapshots.runId, runs.id)).where(and30(
35721
+ eq39(siteAuditSnapshots.projectId, project.id),
35722
+ eq39(runs.kind, RunKinds["site-audit"]),
35445
35723
  inArray14(runs.status, SURFACEABLE_STATUSES),
35446
35724
  notProbeRun()
35447
35725
  )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(2).all();
@@ -35474,9 +35752,9 @@ async function technicalAeoRoutes(app, opts) {
35474
35752
  });
35475
35753
  app.get("/projects/:name/technical-aeo/pages", async (request) => {
35476
35754
  const project = resolveProject(app.db, request.params.name);
35477
- const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq38(siteAuditSnapshots.runId, runs.id)).where(and29(
35478
- eq38(siteAuditSnapshots.projectId, project.id),
35479
- eq38(runs.kind, RunKinds["site-audit"]),
35755
+ const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq39(siteAuditSnapshots.runId, runs.id)).where(and30(
35756
+ eq39(siteAuditSnapshots.projectId, project.id),
35757
+ eq39(runs.kind, RunKinds["site-audit"]),
35480
35758
  inArray14(runs.status, SURFACEABLE_STATUSES),
35481
35759
  notProbeRun()
35482
35760
  )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get();
@@ -35484,14 +35762,14 @@ async function technicalAeoRoutes(app, opts) {
35484
35762
  return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
35485
35763
  }
35486
35764
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
35487
- const conds = [eq38(siteAuditPages.runId, latest.runId)];
35488
- if (statusFilter) conds.push(eq38(siteAuditPages.status, statusFilter));
35489
- const where = and29(...conds);
35765
+ const conds = [eq39(siteAuditPages.runId, latest.runId)];
35766
+ if (statusFilter) conds.push(eq39(siteAuditPages.status, statusFilter));
35767
+ const where = and30(...conds);
35490
35768
  const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
35491
35769
  const total = totalRow?.value ?? 0;
35492
35770
  const limit = parsePositiveInt(request.query.limit, 100, 500);
35493
35771
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
35494
- const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ? asc5(siteAuditPages.url) : asc5(siteAuditPages.overallScore);
35772
+ const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ? asc6(siteAuditPages.url) : asc6(siteAuditPages.overallScore);
35495
35773
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
35496
35774
  const pages = rows.map((row) => ({
35497
35775
  url: row.url,
@@ -35510,9 +35788,9 @@ async function technicalAeoRoutes(app, opts) {
35510
35788
  auditedAt: siteAuditSnapshots.auditedAt,
35511
35789
  aggregateScore: siteAuditSnapshots.aggregateScore,
35512
35790
  pagesAudited: siteAuditSnapshots.pagesAudited
35513
- }).from(siteAuditSnapshots).innerJoin(runs, eq38(siteAuditSnapshots.runId, runs.id)).where(and29(
35514
- eq38(siteAuditSnapshots.projectId, project.id),
35515
- eq38(runs.kind, RunKinds["site-audit"]),
35791
+ }).from(siteAuditSnapshots).innerJoin(runs, eq39(siteAuditSnapshots.runId, runs.id)).where(and30(
35792
+ eq39(siteAuditSnapshots.projectId, project.id),
35793
+ eq39(runs.kind, RunKinds["site-audit"]),
35516
35794
  inArray14(runs.status, SURFACEABLE_STATUSES),
35517
35795
  notProbeRun()
35518
35796
  )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(limit).all();
@@ -35524,9 +35802,9 @@ async function technicalAeoRoutes(app, opts) {
35524
35802
  if (!parsed.success) {
35525
35803
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
35526
35804
  }
35527
- const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and29(
35528
- eq38(runs.projectId, project.id),
35529
- eq38(runs.kind, RunKinds["site-audit"]),
35805
+ const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and30(
35806
+ eq39(runs.projectId, project.id),
35807
+ eq39(runs.kind, RunKinds["site-audit"]),
35530
35808
  inArray14(runs.status, [RunStatuses.queued, RunStatuses.running])
35531
35809
  )).get();
35532
35810
  if (existing) {
@@ -35632,6 +35910,7 @@ async function apiRoutes(app, opts) {
35632
35910
  await api.register(reportRoutes);
35633
35911
  await api.register(citationRoutes);
35634
35912
  await api.register(visibilityStatsRoutes);
35913
+ await api.register(resultsExportRoutes);
35635
35914
  await api.register(compositeRoutes);
35636
35915
  await api.register(contentRoutes, {
35637
35916
  explainContentRecommendation: opts.explainContentRecommendation,
@@ -36118,9 +36397,9 @@ var IntelligenceService = class {
36118
36397
  */
36119
36398
  analyzeAndPersist(runId, projectId) {
36120
36399
  const recentRuns = this.db.select().from(runs).where(
36121
- and30(
36122
- eq39(runs.projectId, projectId),
36123
- or6(eq39(runs.status, "completed"), eq39(runs.status, "partial")),
36400
+ and31(
36401
+ eq40(runs.projectId, projectId),
36402
+ or6(eq40(runs.status, "completed"), eq40(runs.status, "partial")),
36124
36403
  // Defensive: RunCoordinator already skips probes before this is
36125
36404
  // called, but if a future call site invokes analyzeAndPersist
36126
36405
  // directly for a probe, probes still must not pollute the
@@ -36202,7 +36481,7 @@ var IntelligenceService = class {
36202
36481
  * Returns the persisted insights so the coordinator can count critical/high.
36203
36482
  */
36204
36483
  analyzeAndPersistGbp(runId, projectId) {
36205
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq39(runs.id, runId)).get();
36484
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq40(runs.id, runId)).get();
36206
36485
  if (!runRow) {
36207
36486
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
36208
36487
  this.persistGbpInsights(runId, projectId, [], []);
@@ -36210,11 +36489,11 @@ var IntelligenceService = class {
36210
36489
  }
36211
36490
  const windowStart = runRow.startedAt ?? runRow.createdAt;
36212
36491
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
36213
- const selected = this.db.select().from(gbpLocations).where(and30(
36214
- eq39(gbpLocations.projectId, projectId),
36215
- eq39(gbpLocations.selected, true),
36216
- gte7(gbpLocations.syncedAt, windowStart),
36217
- lte4(gbpLocations.syncedAt, windowEnd)
36492
+ const selected = this.db.select().from(gbpLocations).where(and31(
36493
+ eq40(gbpLocations.projectId, projectId),
36494
+ eq40(gbpLocations.selected, true),
36495
+ gte8(gbpLocations.syncedAt, windowStart),
36496
+ lte5(gbpLocations.syncedAt, windowEnd)
36218
36497
  )).all();
36219
36498
  if (selected.length === 0) {
36220
36499
  log.info("gbp-intelligence.skip", { runId, reason: "no locations synced during run" });
@@ -36247,12 +36526,12 @@ var IntelligenceService = class {
36247
36526
  }
36248
36527
  /** Build the per-location signal bundle the GBP analyzer consumes. */
36249
36528
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
36250
- const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and30(eq39(gbpDailyMetrics.projectId, projectId), eq39(gbpDailyMetrics.locationName, locationName))).all();
36251
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and30(eq39(gbpPlaceActions.projectId, projectId), eq39(gbpPlaceActions.locationName, locationName))).all();
36252
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and30(eq39(gbpLodgingSnapshots.projectId, projectId), eq39(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc18(gbpLodgingSnapshots.syncedAt)).limit(1).get();
36253
- const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and30(eq39(gbpLocations.projectId, projectId), eq39(gbpLocations.locationName, locationName))).get();
36529
+ const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and31(eq40(gbpDailyMetrics.projectId, projectId), eq40(gbpDailyMetrics.locationName, locationName))).all();
36530
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and31(eq40(gbpPlaceActions.projectId, projectId), eq40(gbpPlaceActions.locationName, locationName))).all();
36531
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and31(eq40(gbpLodgingSnapshots.projectId, projectId), eq40(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc18(gbpLodgingSnapshots.syncedAt)).limit(1).get();
36532
+ const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and31(eq40(gbpLocations.projectId, projectId), eq40(gbpLocations.locationName, locationName))).get();
36254
36533
  const descriptionMissing = !(ownerRow?.description ?? "").trim();
36255
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and30(eq39(gbpPlaceDetails.projectId, projectId), eq39(gbpPlaceDetails.locationName, locationName))).orderBy(desc18(gbpPlaceDetails.syncedAt)).limit(1).get();
36534
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and31(eq40(gbpPlaceDetails.projectId, projectId), eq40(gbpPlaceDetails.locationName, locationName))).orderBy(desc18(gbpPlaceDetails.syncedAt)).limit(1).get();
36256
36535
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
36257
36536
  const summary = buildGbpSummary({
36258
36537
  locationName,
@@ -36288,7 +36567,7 @@ var IntelligenceService = class {
36288
36567
  /** Build the month-over-month keyword series for a location from the
36289
36568
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
36290
36569
  buildGbpKeywordTrend(projectId, locationName) {
36291
- const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and30(eq39(gbpKeywordMonthly.projectId, projectId), eq39(gbpKeywordMonthly.locationName, locationName))).all();
36570
+ const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and31(eq40(gbpKeywordMonthly.projectId, projectId), eq40(gbpKeywordMonthly.locationName, locationName))).all();
36292
36571
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
36293
36572
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
36294
36573
  const recentMonth = months[0] ?? null;
@@ -36319,7 +36598,7 @@ var IntelligenceService = class {
36319
36598
  */
36320
36599
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
36321
36600
  const covered = new Set(coveredLocationNames);
36322
- const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and30(eq39(insights.projectId, projectId), eq39(insights.provider, GBP_INSIGHT_PROVIDER))).all();
36601
+ const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and31(eq40(insights.projectId, projectId), eq40(insights.provider, GBP_INSIGHT_PROVIDER))).all();
36323
36602
  const staleIds = [];
36324
36603
  const dismissedSlots = /* @__PURE__ */ new Set();
36325
36604
  for (const row of existing) {
@@ -36330,7 +36609,7 @@ var IntelligenceService = class {
36330
36609
  }
36331
36610
  this.db.transaction((tx) => {
36332
36611
  for (const id of staleIds) {
36333
- tx.delete(insights).where(eq39(insights.id, id)).run();
36612
+ tx.delete(insights).where(eq40(insights.id, id)).run();
36334
36613
  }
36335
36614
  for (const insight of gbpInsights) {
36336
36615
  const parsed = parseGbpInsightId(insight.id);
@@ -36408,7 +36687,7 @@ var IntelligenceService = class {
36408
36687
  * create per run + aggregate). DB is left untouched.
36409
36688
  */
36410
36689
  backfill(projectName, opts, onProgress) {
36411
- const project = this.db.select().from(projects).where(eq39(projects.name, projectName)).get();
36690
+ const project = this.db.select().from(projects).where(eq40(projects.name, projectName)).get();
36412
36691
  if (!project) {
36413
36692
  throw new Error(`Project "${projectName}" not found`);
36414
36693
  }
@@ -36421,13 +36700,13 @@ var IntelligenceService = class {
36421
36700
  sinceTimestamp = parsed;
36422
36701
  }
36423
36702
  const allRuns = this.db.select().from(runs).where(
36424
- and30(
36425
- eq39(runs.projectId, project.id),
36426
- or6(eq39(runs.status, "completed"), eq39(runs.status, "partial")),
36703
+ and31(
36704
+ eq40(runs.projectId, project.id),
36705
+ or6(eq40(runs.status, "completed"), eq40(runs.status, "partial")),
36427
36706
  // Backfill must not replay probe runs as if they were real sweeps.
36428
36707
  ne5(runs.trigger, RunTriggers.probe)
36429
36708
  )
36430
- ).orderBy(asc6(runs.finishedAt)).all();
36709
+ ).orderBy(asc7(runs.finishedAt)).all();
36431
36710
  let startIdx = 0;
36432
36711
  let endIdx = allRuns.length;
36433
36712
  if (opts?.fromRunId) {
@@ -36502,7 +36781,7 @@ var IntelligenceService = class {
36502
36781
  return { processed, skipped, totalInsights };
36503
36782
  }
36504
36783
  loadTrackedCompetitors(projectId) {
36505
- return this.db.select({ domain: competitors.domain }).from(competitors).where(eq39(competitors.projectId, projectId)).all().map((r) => r.domain);
36784
+ return this.db.select({ domain: competitors.domain }).from(competitors).where(eq40(competitors.projectId, projectId)).all().map((r) => r.domain);
36506
36785
  }
36507
36786
  /**
36508
36787
  * Wipe transition signals from an analysis result while keeping health.
@@ -36523,15 +36802,15 @@ var IntelligenceService = class {
36523
36802
  }
36524
36803
  persistResult(result, runId, projectId) {
36525
36804
  const previouslyDismissed = /* @__PURE__ */ new Set();
36526
- const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq39(insights.runId, runId)).all();
36805
+ const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq40(insights.runId, runId)).all();
36527
36806
  for (const row of existingInsights) {
36528
36807
  if (row.dismissed) {
36529
36808
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
36530
36809
  }
36531
36810
  }
36532
36811
  this.db.transaction((tx) => {
36533
- tx.delete(insights).where(eq39(insights.runId, runId)).run();
36534
- tx.delete(healthSnapshots).where(eq39(healthSnapshots.runId, runId)).run();
36812
+ tx.delete(insights).where(eq40(insights.runId, runId)).run();
36813
+ tx.delete(healthSnapshots).where(eq40(healthSnapshots.runId, runId)).run();
36535
36814
  const now = (/* @__PURE__ */ new Date()).toISOString();
36536
36815
  for (const insight of result.insights) {
36537
36816
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -36584,24 +36863,24 @@ var IntelligenceService = class {
36584
36863
  applySeverityTiering(rawInsights, excludeRunId, projectId) {
36585
36864
  const regressions = rawInsights.filter((i) => i.type === "regression");
36586
36865
  if (regressions.length === 0) return rawInsights;
36587
- const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq39(gscSearchData.projectId, projectId)).all();
36866
+ const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq40(gscSearchData.projectId, projectId)).all();
36588
36867
  const gscConnected = gscRows.length > 0;
36589
36868
  const gscImpressionsByQuery = /* @__PURE__ */ new Map();
36590
36869
  for (const row of gscRows) {
36591
36870
  const key = row.query.toLowerCase();
36592
36871
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
36593
36872
  }
36594
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq39(projects.id, projectId)).get();
36873
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq40(projects.id, projectId)).get();
36595
36874
  const locationCount = Math.max(
36596
36875
  1,
36597
36876
  (projectRow?.locations ?? []).length
36598
36877
  );
36599
36878
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
36600
36879
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
36601
- and30(
36602
- eq39(runs.projectId, projectId),
36603
- eq39(runs.kind, RunKinds["answer-visibility"]),
36604
- or6(eq39(runs.status, "completed"), eq39(runs.status, "partial")),
36880
+ and31(
36881
+ eq40(runs.projectId, projectId),
36882
+ eq40(runs.kind, RunKinds["answer-visibility"]),
36883
+ or6(eq40(runs.status, "completed"), eq40(runs.status, "partial")),
36605
36884
  // Defensive — see top of file.
36606
36885
  ne5(runs.trigger, RunTriggers.probe)
36607
36886
  )
@@ -36621,7 +36900,7 @@ var IntelligenceService = class {
36621
36900
  const haveHistory = recentRunIds.length > 0;
36622
36901
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
36623
36902
  if (haveHistory) {
36624
- const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and30(eq39(insights.type, "regression"), inArray15(insights.runId, recentRunIds))).all();
36903
+ const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and31(eq40(insights.type, "regression"), inArray15(insights.runId, recentRunIds))).all();
36625
36904
  const regressionGroups = /* @__PURE__ */ new Map();
36626
36905
  for (const row of priorRows) {
36627
36906
  if (!row.runId) continue;
@@ -36650,7 +36929,7 @@ var IntelligenceService = class {
36650
36929
  });
36651
36930
  }
36652
36931
  buildRunData(runId, projectId, completedAt, location = null) {
36653
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq39(projects.id, projectId)).get();
36932
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq40(projects.id, projectId)).get();
36654
36933
  const projectDomains = projectDomainRow ? effectiveDomains({
36655
36934
  canonicalDomain: projectDomainRow.canonicalDomain,
36656
36935
  ownedDomains: projectDomainRow.ownedDomains
@@ -36667,7 +36946,7 @@ var IntelligenceService = class {
36667
36946
  citedDomains: querySnapshots.citedDomains,
36668
36947
  competitorOverlap: querySnapshots.competitorOverlap,
36669
36948
  snapshotLocation: querySnapshots.location
36670
- }).from(querySnapshots).leftJoin(queries, eq39(querySnapshots.queryId, queries.id)).where(eq39(querySnapshots.runId, runId)).all();
36949
+ }).from(querySnapshots).leftJoin(queries, eq40(querySnapshots.queryId, queries.id)).where(eq40(querySnapshots.runId, runId)).all();
36671
36950
  const snapshots = [];
36672
36951
  let orphanCount = 0;
36673
36952
  for (const r of rows) {