@ainyc/canonry 4.100.1 → 4.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/README.md +4 -1
  2. package/assets/assets/{BacklinksPage-v2RC8wlg.js → BacklinksPage-lXt19SLR.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-B74e5pqn.js → ChartPrimitives-C-sLgrDV.js} +1 -1
  4. package/assets/assets/ProjectPage-wtvaWIRA.js +7 -0
  5. package/assets/assets/{RunRow-BmVPjO-m.js → RunRow-Db32k-uH.js} +1 -1
  6. package/assets/assets/{RunsPage-CK5atXvL.js → RunsPage-DwW3Mely.js} +1 -1
  7. package/assets/assets/{SettingsPage-DarPFUPl.js → SettingsPage-gXn6OGsu.js} +1 -1
  8. package/assets/assets/{TrafficPage-Ddnn_WqU.js → TrafficPage-I49ci4gj.js} +1 -1
  9. package/assets/assets/{TrafficSourceDetailPage-g08SKnwZ.js → TrafficSourceDetailPage-BCvLztx9.js} +1 -1
  10. package/assets/assets/{arrow-left-D39Vxasb.js → arrow-left-BpA06YY0.js} +1 -1
  11. package/assets/assets/{extract-error-message-BiNWqBeh.js → extract-error-message-BZSIQ5iV.js} +1 -1
  12. package/assets/assets/{index-Dd4J1VhB.js → index-DSSLug80.js} +64 -64
  13. package/assets/assets/{trash-2-xh6gsnap.js → trash-2-CLkUNdvb.js} +1 -1
  14. package/assets/index.html +1 -1
  15. package/dist/{chunk-IDI6LMYL.js → chunk-2FUODJQR.js} +6 -2
  16. package/dist/{chunk-ALJSES3X.js → chunk-3PVCILSN.js} +565 -51
  17. package/dist/{chunk-YBIT5O62.js → chunk-B2H2RRIT.js} +927 -614
  18. package/dist/{chunk-JF7EXHGA.js → chunk-EOQXB63S.js} +30 -2
  19. package/dist/cli.js +44 -11
  20. package/dist/index.js +4 -4
  21. package/dist/{intelligence-service-TWCEKVKZ.js → intelligence-service-RC4BY5HF.js} +2 -2
  22. package/dist/mcp.js +2 -2
  23. package/package.json +10 -10
  24. package/assets/assets/ProjectPage-BCzS7TQn.js +0 -6
@@ -263,10 +263,10 @@ import {
263
263
  wordpressSchemaDeployResultDtoSchema,
264
264
  wordpressSchemaStatusResultDtoSchema,
265
265
  wordpressStatusDtoSchema
266
- } from "./chunk-JF7EXHGA.js";
266
+ } from "./chunk-EOQXB63S.js";
267
267
 
268
268
  // src/intelligence-service.ts
269
- import { eq as eq37, desc as desc18, asc as asc5, and as and27, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
269
+ import { eq as eq38, desc as desc18, asc as asc6, and as and28, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
270
270
 
271
271
  // ../db/src/client.ts
272
272
  import { mkdirSync } from "fs";
@@ -284,6 +284,7 @@ __export(schema_exports, {
284
284
  adsInsightsDaily: () => adsInsightsDaily,
285
285
  agentMemory: () => agentMemory,
286
286
  agentSessions: () => agentSessions,
287
+ agentToolEvents: () => agentToolEvents,
287
288
  aiReferralEventsHourly: () => aiReferralEventsHourly,
288
289
  aiUserFetchEventsHourly: () => aiUserFetchEventsHourly,
289
290
  apiKeys: () => apiKeys,
@@ -317,10 +318,12 @@ __export(schema_exports, {
317
318
  gbpPlaceDetails: () => gbpPlaceDetails,
318
319
  googleConnections: () => googleConnections,
319
320
  gscCoverageSnapshots: () => gscCoverageSnapshots,
321
+ gscDailyTotals: () => gscDailyTotals,
320
322
  gscSearchData: () => gscSearchData,
321
323
  gscUrlInspections: () => gscUrlInspections,
322
324
  healthSnapshots: () => healthSnapshots,
323
325
  insights: () => insights,
326
+ llmUsageEvents: () => llmUsageEvents,
324
327
  migrationsTable: () => migrationsTable,
325
328
  notifications: () => notifications,
326
329
  projects: () => projects,
@@ -466,11 +469,20 @@ var apiKeys = sqliteTable("api_keys", {
466
469
  keyHash: text("key_hash").notNull().unique(),
467
470
  keyPrefix: text("key_prefix").notNull(),
468
471
  scopes: text("scopes", { mode: "json" }).$type().notNull().default(["*"]),
472
+ /**
473
+ * When set, the key is scoped to a SINGLE project: every project-scoped read
474
+ * or write is gated to this project id (enforced centrally in `auth.ts`).
475
+ * NULL keeps full-instance access — the historical default for every key
476
+ * `canonry init` / `canonry key create` writes. Cascade-deletes with the
477
+ * project so a stale scoped key never outlives its project.
478
+ */
479
+ projectId: text("project_id").references(() => projects.id, { onDelete: "cascade" }),
469
480
  createdAt: text("created_at").notNull(),
470
481
  lastUsedAt: text("last_used_at"),
471
482
  revokedAt: text("revoked_at")
472
483
  }, (table) => [
473
- index("idx_api_keys_prefix").on(table.keyPrefix)
484
+ index("idx_api_keys_prefix").on(table.keyPrefix),
485
+ index("idx_api_keys_project").on(table.projectId)
474
486
  ]);
475
487
  var schedules = sqliteTable("schedules", {
476
488
  id: text("id").primaryKey(),
@@ -546,6 +558,20 @@ var gscSearchData = sqliteTable("gsc_search_data", {
546
558
  index("idx_gsc_search_query").on(table.query),
547
559
  index("idx_gsc_search_run").on(table.syncRunId)
548
560
  ]);
561
+ var gscDailyTotals = sqliteTable("gsc_daily_totals", {
562
+ id: text("id").primaryKey(),
563
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
564
+ date: text("date").notNull(),
565
+ clicks: integer("clicks").notNull(),
566
+ impressions: integer("impressions").notNull(),
567
+ // Stored as a string like `gscSearchData.position` (parsed to a number on
568
+ // read). CTR is derived (clicks / impressions) and intentionally not stored.
569
+ position: text("position").notNull(),
570
+ createdAt: text("created_at").notNull()
571
+ }, (table) => [
572
+ uniqueIndex("idx_gsc_daily_totals_project_date").on(table.projectId, table.date),
573
+ index("idx_gsc_daily_totals_project").on(table.projectId)
574
+ ]);
549
575
  var gscUrlInspections = sqliteTable("gsc_url_inspections", {
550
576
  id: text("id").primaryKey(),
551
577
  projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
@@ -924,6 +950,53 @@ var agentMemory = sqliteTable("agent_memory", {
924
950
  uniqueIndex("uniq_agent_memory_project_key").on(table.projectId, table.key),
925
951
  index("idx_agent_memory_project_updated").on(table.projectId, table.updatedAt)
926
952
  ]);
953
+ var llmUsageEvents = sqliteTable("llm_usage_events", {
954
+ id: text("id").primaryKey(),
955
+ projectId: text("project_id").references(() => projects.id, { onDelete: "cascade" }),
956
+ runId: text("run_id").references(() => runs.id, { onDelete: "set null" }),
957
+ agentSessionId: text("agent_session_id").references(() => agentSessions.id, { onDelete: "set null" }),
958
+ feature: text("feature").notNull(),
959
+ provider: text("provider").notNull(),
960
+ model: text("model").notNull(),
961
+ responseId: text("response_id"),
962
+ inputTokens: integer("input_tokens").notNull().default(0),
963
+ outputTokens: integer("output_tokens").notNull().default(0),
964
+ cacheReadTokens: integer("cache_read_tokens").notNull().default(0),
965
+ cacheWriteTokens: integer("cache_write_tokens").notNull().default(0),
966
+ totalTokens: integer("total_tokens").notNull().default(0),
967
+ costMillicents: integer("cost_millicents").notNull().default(0),
968
+ promptFamily: text("prompt_family"),
969
+ promptVersion: text("prompt_version"),
970
+ metadata: text("metadata", { mode: "json" }).$type(),
971
+ createdAt: text("created_at").notNull()
972
+ }, (table) => [
973
+ index("idx_llm_usage_project_created").on(table.projectId, table.createdAt),
974
+ index("idx_llm_usage_feature_created").on(table.feature, table.createdAt),
975
+ index("idx_llm_usage_session_created").on(table.agentSessionId, table.createdAt),
976
+ index("idx_llm_usage_run_created").on(table.runId, table.createdAt)
977
+ ]);
978
+ var agentToolEvents = sqliteTable("agent_tool_events", {
979
+ id: text("id").primaryKey(),
980
+ projectId: text("project_id").references(() => projects.id, { onDelete: "cascade" }),
981
+ agentSessionId: text("agent_session_id").references(() => agentSessions.id, { onDelete: "set null" }),
982
+ toolCallId: text("tool_call_id").notNull(),
983
+ toolName: text("tool_name").notNull(),
984
+ assistantResponseId: text("assistant_response_id"),
985
+ provider: text("provider"),
986
+ model: text("model"),
987
+ status: text("status").notNull(),
988
+ durationMs: integer("duration_ms").notNull().default(0),
989
+ argsBytes: integer("args_bytes").notNull().default(0),
990
+ resultTextChars: integer("result_text_chars").notNull().default(0),
991
+ resultBytes: integer("result_bytes").notNull().default(0),
992
+ metadata: text("metadata", { mode: "json" }).$type(),
993
+ createdAt: text("created_at").notNull()
994
+ }, (table) => [
995
+ index("idx_agent_tool_events_project_created").on(table.projectId, table.createdAt),
996
+ index("idx_agent_tool_events_session_created").on(table.agentSessionId, table.createdAt),
997
+ index("idx_agent_tool_events_tool_created").on(table.toolName, table.createdAt),
998
+ index("idx_agent_tool_events_status_created").on(table.status, table.createdAt)
999
+ ]);
927
1000
  var trafficSources = sqliteTable("traffic_sources", {
928
1001
  id: text("id").primaryKey(),
929
1002
  projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
@@ -3211,6 +3284,91 @@ var MIGRATION_VERSIONS = [
3211
3284
  `ALTER TABLE ads_insights_daily ADD COLUMN conversions INTEGER NOT NULL DEFAULT 0`,
3212
3285
  `ALTER TABLE ads_connections ADD COLUMN conversion_tracking_configured INTEGER NOT NULL DEFAULT 0`
3213
3286
  ]
3287
+ },
3288
+ {
3289
+ version: 84,
3290
+ name: "llm-usage-events",
3291
+ statements: [
3292
+ `CREATE TABLE IF NOT EXISTS llm_usage_events (
3293
+ id TEXT PRIMARY KEY,
3294
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
3295
+ run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
3296
+ agent_session_id TEXT REFERENCES agent_sessions(id) ON DELETE SET NULL,
3297
+ feature TEXT NOT NULL,
3298
+ provider TEXT NOT NULL,
3299
+ model TEXT NOT NULL,
3300
+ response_id TEXT,
3301
+ input_tokens INTEGER NOT NULL DEFAULT 0,
3302
+ output_tokens INTEGER NOT NULL DEFAULT 0,
3303
+ cache_read_tokens INTEGER NOT NULL DEFAULT 0,
3304
+ cache_write_tokens INTEGER NOT NULL DEFAULT 0,
3305
+ total_tokens INTEGER NOT NULL DEFAULT 0,
3306
+ cost_millicents INTEGER NOT NULL DEFAULT 0,
3307
+ prompt_family TEXT,
3308
+ prompt_version TEXT,
3309
+ metadata TEXT,
3310
+ created_at TEXT NOT NULL
3311
+ )`,
3312
+ `CREATE INDEX IF NOT EXISTS idx_llm_usage_project_created ON llm_usage_events(project_id, created_at)`,
3313
+ `CREATE INDEX IF NOT EXISTS idx_llm_usage_feature_created ON llm_usage_events(feature, created_at)`,
3314
+ `CREATE INDEX IF NOT EXISTS idx_llm_usage_session_created ON llm_usage_events(agent_session_id, created_at)`,
3315
+ `CREATE INDEX IF NOT EXISTS idx_llm_usage_run_created ON llm_usage_events(run_id, created_at)`
3316
+ ]
3317
+ },
3318
+ {
3319
+ version: 85,
3320
+ name: "agent-tool-events",
3321
+ statements: [
3322
+ `CREATE TABLE IF NOT EXISTS agent_tool_events (
3323
+ id TEXT PRIMARY KEY,
3324
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
3325
+ agent_session_id TEXT REFERENCES agent_sessions(id) ON DELETE SET NULL,
3326
+ tool_call_id TEXT NOT NULL,
3327
+ tool_name TEXT NOT NULL,
3328
+ assistant_response_id TEXT,
3329
+ provider TEXT,
3330
+ model TEXT,
3331
+ status TEXT NOT NULL,
3332
+ duration_ms INTEGER NOT NULL DEFAULT 0,
3333
+ args_bytes INTEGER NOT NULL DEFAULT 0,
3334
+ result_text_chars INTEGER NOT NULL DEFAULT 0,
3335
+ result_bytes INTEGER NOT NULL DEFAULT 0,
3336
+ metadata TEXT,
3337
+ created_at TEXT NOT NULL
3338
+ )`,
3339
+ `CREATE INDEX IF NOT EXISTS idx_agent_tool_events_project_created ON agent_tool_events(project_id, created_at)`,
3340
+ `CREATE INDEX IF NOT EXISTS idx_agent_tool_events_session_created ON agent_tool_events(agent_session_id, created_at)`,
3341
+ `CREATE INDEX IF NOT EXISTS idx_agent_tool_events_tool_created ON agent_tool_events(tool_name, created_at)`,
3342
+ `CREATE INDEX IF NOT EXISTS idx_agent_tool_events_status_created ON agent_tool_events(status, created_at)`
3343
+ ]
3344
+ },
3345
+ {
3346
+ version: 86,
3347
+ name: "gsc-daily-totals",
3348
+ statements: [
3349
+ `CREATE TABLE IF NOT EXISTS gsc_daily_totals (
3350
+ id TEXT PRIMARY KEY,
3351
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
3352
+ date TEXT NOT NULL,
3353
+ clicks INTEGER NOT NULL,
3354
+ impressions INTEGER NOT NULL,
3355
+ position TEXT NOT NULL,
3356
+ created_at TEXT NOT NULL
3357
+ )`,
3358
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_gsc_daily_totals_project_date ON gsc_daily_totals(project_id, date)`,
3359
+ `CREATE INDEX IF NOT EXISTS idx_gsc_daily_totals_project ON gsc_daily_totals(project_id)`
3360
+ ]
3361
+ },
3362
+ {
3363
+ version: 87,
3364
+ name: "api-key-project-scope",
3365
+ statements: [
3366
+ // Opt-in single-project scoping for API keys. NULL = full-instance access
3367
+ // (the historical default), so this is a no-op for every existing key.
3368
+ // ALTER ADD COLUMN is idempotent (the runner swallows duplicate-column on retry).
3369
+ `ALTER TABLE api_keys ADD COLUMN project_id TEXT REFERENCES projects(id) ON DELETE CASCADE`,
3370
+ `CREATE INDEX IF NOT EXISTS idx_api_keys_project ON api_keys(project_id)`
3371
+ ]
3214
3372
  }
3215
3373
  ];
3216
3374
  function rebuildBacklinkTableWithSource(tx, table) {
@@ -5545,6 +5703,12 @@ function requireScope(request, scope) {
5545
5703
  if (key.scopes.includes("*") || key.scopes.includes(scope)) return;
5546
5704
  throw forbidden(`This action requires the "${scope}" scope on your API key.`);
5547
5705
  }
5706
+ function assertProjectScope(request, projectId) {
5707
+ const scoped = request.apiKey?.projectId;
5708
+ if (scoped && scoped !== projectId) {
5709
+ throw forbidden("This API key is scoped to a different project.");
5710
+ }
5711
+ }
5548
5712
  function hashApiKey(key) {
5549
5713
  return crypto2.createHash("sha256").update(key).digest("hex");
5550
5714
  }
@@ -5605,10 +5769,20 @@ async function authPlugin(app, opts = {}) {
5605
5769
  }
5606
5770
  app.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(apiKeys.id, key.id)).run();
5607
5771
  const scopes = Array.isArray(key.scopes) ? key.scopes : [];
5608
- request.apiKey = { id: key.id, name: key.name, scopes };
5772
+ request.apiKey = { id: key.id, name: key.name, scopes, projectId: key.projectId ?? null };
5609
5773
  if (isReadOnlyKey(scopes) && WRITE_METHODS.has(request.method)) {
5610
5774
  throw forbidden("This API key is read-only and cannot perform write operations.");
5611
5775
  }
5776
+ if (key.projectId) {
5777
+ const match = url.match(/\/projects\/([^/?#]+)/);
5778
+ if (match) {
5779
+ const projectName = decodeURIComponent(match[1]);
5780
+ const scoped = app.db.select({ id: projects.id }).from(projects).where(eq(projects.name, projectName)).get();
5781
+ if (!scoped || scoped.id !== key.projectId) {
5782
+ throw forbidden("This API key is scoped to a different project.");
5783
+ }
5784
+ }
5785
+ }
5612
5786
  });
5613
5787
  }
5614
5788
 
@@ -5800,8 +5974,9 @@ async function projectRoutes(app, opts) {
5800
5974
  const created = app.db.select().from(projects).where(eq3(projects.id, id)).get();
5801
5975
  return reply.status(201).send(formatProject(created));
5802
5976
  });
5803
- app.get("/projects", async (_request, reply) => {
5804
- const rows = app.db.select().from(projects).all();
5977
+ app.get("/projects", async (request, reply) => {
5978
+ const scoped = request.apiKey?.projectId;
5979
+ const rows = scoped ? app.db.select().from(projects).where(eq3(projects.id, scoped)).all() : app.db.select().from(projects).all();
5805
5980
  return reply.send(rows.map(formatProject));
5806
5981
  });
5807
5982
  app.get("/projects/:name", async (request, reply) => {
@@ -6638,11 +6813,14 @@ async function runRoutes(app, opts) {
6638
6813
  const filters = [gte(runs.createdAt, since)];
6639
6814
  if (!includeProbe) filters.push(notProbeRun());
6640
6815
  if (kind) filters.push(eq7(runs.kind, kind));
6816
+ const scopedProjectId = request.apiKey?.projectId;
6817
+ if (scopedProjectId) filters.push(eq7(runs.projectId, scopedProjectId));
6641
6818
  const rows = app.db.select().from(runs).where(and2(...filters)).orderBy(desc(runs.createdAt), desc(runs.id)).limit(limit).all();
6642
6819
  return reply.send(rows.map(formatRun));
6643
6820
  });
6644
6821
  app.post("/runs", async (request, reply) => {
6645
- const allProjects = app.db.select().from(projects).all();
6822
+ const scopedProjectId = request.apiKey?.projectId;
6823
+ const allProjects = (scopedProjectId ? app.db.select().from(projects).where(eq7(projects.id, scopedProjectId)) : app.db.select().from(projects)).all();
6646
6824
  if (allProjects.length === 0) {
6647
6825
  return reply.status(207).send([]);
6648
6826
  }
@@ -6708,6 +6886,7 @@ async function runRoutes(app, opts) {
6708
6886
  app.post("/runs/:id/cancel", async (request, reply) => {
6709
6887
  const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
6710
6888
  if (!run) throw notFound("Run", request.params.id);
6889
+ assertProjectScope(request, run.projectId);
6711
6890
  const terminalStatuses = /* @__PURE__ */ new Set(["completed", "partial", "failed", "cancelled"]);
6712
6891
  if (terminalStatuses.has(run.status)) throw runNotCancellable(run.id, run.status);
6713
6892
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -6725,6 +6904,7 @@ async function runRoutes(app, opts) {
6725
6904
  app.get("/runs/:id", async (request, reply) => {
6726
6905
  const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
6727
6906
  if (!run) throw notFound("Run", request.params.id);
6907
+ assertProjectScope(request, run.projectId);
6728
6908
  return reply.send(loadRunDetail(app, run));
6729
6909
  });
6730
6910
  }
@@ -7180,6 +7360,13 @@ async function applyRoutes(app, opts) {
7180
7360
  const now = (/* @__PURE__ */ new Date()).toISOString();
7181
7361
  const name = config.metadata.name;
7182
7362
  const configQueries = resolveConfigSpecQueries(config.spec);
7363
+ const scopedProjectId = request.apiKey?.projectId;
7364
+ if (scopedProjectId) {
7365
+ const target = app.db.select({ id: projects.id }).from(projects).where(eq8(projects.name, name)).get();
7366
+ if (!target || target.id !== scopedProjectId) {
7367
+ throw forbidden("This API key is scoped to a single project and cannot apply this config.");
7368
+ }
7369
+ }
7183
7370
  let projectId;
7184
7371
  let scheduleAction = null;
7185
7372
  let aliasesChanged = false;
@@ -7443,8 +7630,9 @@ async function historyRoutes(app) {
7443
7630
  const rows = app.db.select().from(auditLog).where(eq9(auditLog.projectId, project.id)).orderBy(desc2(auditLog.createdAt)).all();
7444
7631
  return reply.send(rows.map(formatAuditEntry));
7445
7632
  });
7446
- app.get("/history", async (_request, reply) => {
7447
- const rows = app.db.select().from(auditLog).orderBy(desc2(auditLog.createdAt)).all();
7633
+ app.get("/history", async (request, reply) => {
7634
+ const scopedProjectId = request.apiKey?.projectId;
7635
+ const rows = app.db.select().from(auditLog).where(scopedProjectId ? eq9(auditLog.projectId, scopedProjectId) : void 0).orderBy(desc2(auditLog.createdAt)).all();
7448
7636
  return reply.send(rows.map(formatAuditEntry));
7449
7637
  });
7450
7638
  app.get("/projects/:name/snapshots", async (request, reply) => {
@@ -7625,6 +7813,15 @@ async function historyRoutes(app) {
7625
7813
  if (!run1 || !run2) {
7626
7814
  throw validationError("Both run1 and run2 query params are required");
7627
7815
  }
7816
+ const requestedRunIds = [.../* @__PURE__ */ new Set([run1, run2])];
7817
+ const runRows = app.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(inArray2(runs.id, requestedRunIds)).all();
7818
+ const runsById = new Map(runRows.map((row) => [row.id, row]));
7819
+ for (const runId of requestedRunIds) {
7820
+ const run = runsById.get(runId);
7821
+ if (!run || run.projectId !== project.id) {
7822
+ throw notFound("Run", runId);
7823
+ }
7824
+ }
7628
7825
  const snaps1 = app.db.select({
7629
7826
  queryId: querySnapshots.queryId,
7630
7827
  query: queries.query,
@@ -7736,6 +7933,10 @@ async function analyticsRoutes(app) {
7736
7933
  }));
7737
7934
  const projectQueries = app.db.select({ id: queries.id, createdAt: queries.createdAt }).from(queries).where(eq10(queries.projectId, project.id)).all();
7738
7935
  const queryCreatedAt = new Map(projectQueries.map((q) => [q.id, q.createdAt]));
7936
+ const mentionShareCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq10(competitors.projectId, project.id)).all().map((c) => ({
7937
+ domain: c.domain,
7938
+ brandTokens: [brandLabelFromDomain(c.domain)].filter((t) => t.length >= 3)
7939
+ }));
7739
7940
  const overall = computeProviderMetric(allSnapshots);
7740
7941
  const byProvider = {};
7741
7942
  const providers = new Set(allSnapshots.map((s) => s.provider));
@@ -7746,7 +7947,7 @@ async function analyticsRoutes(app) {
7746
7947
  const latest = new Date(projectRuns[projectRuns.length - 1].createdAt);
7747
7948
  const spanDays = Math.max(1, Math.ceil((latest.getTime() - earliest.getTime()) / 864e5));
7748
7949
  const bucketSize = bucketSizeForSpan(spanDays);
7749
- const buckets = computeBuckets(allSnapshots, projectRuns, bucketSize, queryCreatedAt);
7950
+ const buckets = computeBuckets(allSnapshots, projectRuns, bucketSize, queryCreatedAt, mentionShareCompetitors);
7750
7951
  const trend = computeTrend(buckets, "citationRate");
7751
7952
  const mentionTrend = computeTrend(buckets, "mentionRate");
7752
7953
  const queryChanges = computeQueryChanges(projectQueries, cutoff);
@@ -7993,7 +8194,7 @@ function computeProviderMetric(snapshots) {
7993
8194
  mentionedCount
7994
8195
  };
7995
8196
  }
7996
- function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt) {
8197
+ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt, mentionShareCompetitors = []) {
7997
8198
  if (projectRuns.length === 0) return [];
7998
8199
  const earliest = new Date(projectRuns[0].createdAt);
7999
8200
  const latest = new Date(projectRuns[projectRuns.length - 1].createdAt);
@@ -8030,6 +8231,7 @@ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt) {
8030
8231
  queryCount,
8031
8232
  mentionRate: metric.mentionRate,
8032
8233
  mentionedCount: metric.mentionedCount,
8234
+ mentionShare: computeMentionShareBucketMetric(usable, mentionShareCompetitors),
8033
8235
  byProvider
8034
8236
  });
8035
8237
  }
@@ -8037,6 +8239,26 @@ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt) {
8037
8239
  }
8038
8240
  return buckets;
8039
8241
  }
8242
+ function computeMentionShareBucketMetric(snapshots, mentionShareCompetitors) {
8243
+ if (mentionShareCompetitors.length === 0) {
8244
+ return { rate: null, projectMentionSnapshots: 0, competitorMentionSnapshots: 0 };
8245
+ }
8246
+ const result = buildMentionShare(
8247
+ snapshots.map((s) => ({
8248
+ projectMentioned: s.resolvedMentioned,
8249
+ answerText: s.answerText
8250
+ })),
8251
+ { competitors: mentionShareCompetitors }
8252
+ );
8253
+ const projectMentionSnapshots = result.breakdown.projectMentionSnapshots;
8254
+ const competitorMentionSnapshots = result.breakdown.competitorMentionSnapshots;
8255
+ const denominator = projectMentionSnapshots + competitorMentionSnapshots;
8256
+ return {
8257
+ rate: denominator > 0 ? round4(projectMentionSnapshots / denominator) : null,
8258
+ projectMentionSnapshots,
8259
+ competitorMentionSnapshots
8260
+ };
8261
+ }
8040
8262
  function computeQueryChanges(projectQueries, cutoff) {
8041
8263
  const byDay = /* @__PURE__ */ new Map();
8042
8264
  for (const q of projectQueries) {
@@ -8338,7 +8560,7 @@ async function intelligenceRoutes(app) {
8338
8560
  }
8339
8561
 
8340
8562
  // ../api-routes/src/report.ts
8341
- import { and as and9, desc as desc7, eq as eq14, gte as gte2, inArray as inArray6, lt, lte, ne as ne2, or as or3, sql as sql6 } from "drizzle-orm";
8563
+ import { and as and10, desc as desc7, eq as eq15, gte as gte2, inArray as inArray6, lt, lte, ne as ne2, or as or3, sql as sql7 } from "drizzle-orm";
8342
8564
 
8343
8565
  // ../api-routes/src/content.ts
8344
8566
  import crypto11 from "crypto";
@@ -9073,6 +9295,38 @@ function winnabilityClassRank(winnabilityClass) {
9073
9295
  return winnabilityClass === WinnabilityClasses.ownable ? 0 : 1;
9074
9296
  }
9075
9297
 
9298
+ // ../api-routes/src/gsc-totals.ts
9299
+ import { and as and9, asc as asc2, eq as eq14, sql as sql6 } from "drizzle-orm";
9300
+ function readGscDailyTotals(db, projectId, startDate, endDate) {
9301
+ const rows = db.select({
9302
+ date: gscDailyTotals.date,
9303
+ clicks: gscDailyTotals.clicks,
9304
+ impressions: gscDailyTotals.impressions,
9305
+ position: gscDailyTotals.position
9306
+ }).from(gscDailyTotals).where(
9307
+ and9(
9308
+ eq14(gscDailyTotals.projectId, projectId),
9309
+ sql6`${gscDailyTotals.date} >= ${startDate}`,
9310
+ sql6`${gscDailyTotals.date} <= ${endDate}`
9311
+ )
9312
+ ).orderBy(asc2(gscDailyTotals.date)).all();
9313
+ return rows.map((r) => {
9314
+ const position = Number(r.position);
9315
+ return {
9316
+ date: r.date,
9317
+ clicks: r.clicks,
9318
+ impressions: r.impressions,
9319
+ position: Number.isFinite(position) ? position : 0
9320
+ };
9321
+ });
9322
+ }
9323
+ function mergeGscDailyTotalsWithFallback(propertyTotals, dimensionedFallback) {
9324
+ const byDate = /* @__PURE__ */ new Map();
9325
+ for (const row of dimensionedFallback) byDate.set(row.date, row);
9326
+ for (const row of propertyTotals) byDate.set(row.date, row);
9327
+ return [...byDate.values()].sort((a, b) => a.date.localeCompare(b.date));
9328
+ }
9329
+
9076
9330
  // ../api-routes/src/report-renderer.ts
9077
9331
  var COLORS = {
9078
9332
  bg: "#09090b",
@@ -11384,38 +11638,56 @@ function loadSnapshotsForRunIds(db, runIds) {
11384
11638
  }));
11385
11639
  }
11386
11640
  function loadQueryLookup(db, projectId) {
11387
- const rows = db.select().from(queries).where(eq14(queries.projectId, projectId)).all();
11641
+ const rows = db.select().from(queries).where(eq15(queries.projectId, projectId)).all();
11388
11642
  const byId = /* @__PURE__ */ new Map();
11389
11643
  for (const row of rows) byId.set(row.id, row.query);
11390
11644
  return { byId };
11391
11645
  }
11392
11646
  function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trackedQueries, windowDays) {
11393
- const allRows = db.select().from(gscSearchData).where(eq14(gscSearchData.projectId, projectId)).all();
11394
- if (allRows.length === 0) return null;
11647
+ const allRows = db.select().from(gscSearchData).where(eq15(gscSearchData.projectId, projectId)).all();
11648
+ const allDailyTotals = readGscDailyTotals(db, projectId, "", "9999-12-31");
11395
11649
  let maxDate = "";
11396
11650
  for (const r of allRows) if (r.date > maxDate) maxDate = r.date;
11651
+ for (const r of allDailyTotals) if (r.date > maxDate) maxDate = r.date;
11652
+ if (!maxDate) return null;
11397
11653
  const startDate = windowStartDate(maxDate, windowDays);
11398
11654
  const rows = allRows.filter((r) => r.date >= startDate && r.date <= maxDate);
11399
- if (rows.length === 0) return null;
11400
- let totalClicks = 0;
11401
- let totalImpressions = 0;
11402
- let weightedPositionSum = 0;
11655
+ const dailyTotals = allDailyTotals.filter((r) => r.date >= startDate && r.date <= maxDate);
11656
+ if (rows.length === 0 && dailyTotals.length === 0) return null;
11657
+ let dimensionedClicks = 0;
11403
11658
  const queryAgg = /* @__PURE__ */ new Map();
11404
- const trendAgg = /* @__PURE__ */ new Map();
11659
+ const dimensionedTrendAgg = /* @__PURE__ */ new Map();
11405
11660
  for (const r of rows) {
11406
- totalClicks += r.clicks;
11407
- totalImpressions += r.impressions;
11408
- weightedPositionSum += safeNum(r.position) * r.impressions;
11661
+ dimensionedClicks += r.clicks;
11409
11662
  const q = queryAgg.get(r.query) ?? { clicks: 0, impressions: 0, weightedPositionSum: 0 };
11410
11663
  q.clicks += r.clicks;
11411
11664
  q.impressions += r.impressions;
11412
11665
  q.weightedPositionSum += safeNum(r.position) * r.impressions;
11413
11666
  queryAgg.set(r.query, q);
11414
- const t = trendAgg.get(r.date) ?? { clicks: 0, impressions: 0 };
11667
+ const t = dimensionedTrendAgg.get(r.date) ?? { clicks: 0, impressions: 0, weightedPositionSum: 0 };
11415
11668
  t.clicks += r.clicks;
11416
11669
  t.impressions += r.impressions;
11417
- trendAgg.set(r.date, t);
11670
+ t.weightedPositionSum += safeNum(r.position) * r.impressions;
11671
+ dimensionedTrendAgg.set(r.date, t);
11672
+ }
11673
+ const dailySeries = mergeGscDailyTotalsWithFallback(
11674
+ dailyTotals,
11675
+ [...dimensionedTrendAgg.entries()].map(([date, agg]) => ({
11676
+ date,
11677
+ clicks: agg.clicks,
11678
+ impressions: agg.impressions,
11679
+ position: agg.impressions > 0 ? agg.weightedPositionSum / agg.impressions : 0
11680
+ }))
11681
+ );
11682
+ let totalClicks = 0;
11683
+ let totalImpressions = 0;
11684
+ let weightedPositionSum = 0;
11685
+ for (const d of dailySeries) {
11686
+ totalClicks += d.clicks;
11687
+ totalImpressions += d.impressions;
11688
+ weightedPositionSum += d.position * d.impressions;
11418
11689
  }
11690
+ const trend = dailySeries.map((d) => ({ date: d.date, clicks: d.clicks, impressions: d.impressions }));
11419
11691
  const ctr = totalImpressions > 0 ? totalClicks / totalImpressions : 0;
11420
11692
  const avgPosition = totalImpressions > 0 ? weightedPositionSum / totalImpressions : 0;
11421
11693
  const topQueries = [...queryAgg.entries()].map(([query, agg]) => ({
@@ -11438,9 +11710,8 @@ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trac
11438
11710
  category,
11439
11711
  clicks: agg.clicks,
11440
11712
  impressions: agg.impressions,
11441
- sharePct: totalClicks > 0 ? Math.round(agg.clicks / totalClicks * 100) : 0
11713
+ sharePct: dimensionedClicks > 0 ? Math.round(agg.clicks / dimensionedClicks * 100) : 0
11442
11714
  })).sort((a, b) => b.clicks - a.clicks);
11443
- const trend = [...trendAgg.entries()].map(([date, agg]) => ({ date, clicks: agg.clicks, impressions: agg.impressions })).sort((a, b) => a.date.localeCompare(b.date));
11444
11715
  const periodStart = trend[0]?.date ?? "";
11445
11716
  const periodEnd = trend.at(-1)?.date ?? "";
11446
11717
  const trackedSet = new Set(trackedQueries.map((q) => q.toLowerCase()));
@@ -11464,13 +11735,13 @@ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trac
11464
11735
  function buildGaSection(db, projectId, windowDays) {
11465
11736
  const gaWindowKey = GA_WINDOW_SUMMARY_KEYS[windowDays];
11466
11737
  const windowSummary = gaWindowKey ? db.select().from(gaTrafficWindowSummaries).where(
11467
- and9(
11468
- eq14(gaTrafficWindowSummaries.projectId, projectId),
11469
- eq14(gaTrafficWindowSummaries.windowKey, gaWindowKey)
11738
+ and10(
11739
+ eq15(gaTrafficWindowSummaries.projectId, projectId),
11740
+ eq15(gaTrafficWindowSummaries.windowKey, gaWindowKey)
11470
11741
  )
11471
11742
  ).limit(1).get() : void 0;
11472
- const fallbackSummary = windowSummary ? null : db.select().from(gaTrafficSummaries).where(eq14(gaTrafficSummaries.projectId, projectId)).orderBy(desc7(gaTrafficSummaries.syncedAt)).limit(1).get();
11473
- const allSnapshotRows = db.select().from(gaTrafficSnapshots).where(eq14(gaTrafficSnapshots.projectId, projectId)).all();
11743
+ const fallbackSummary = windowSummary ? null : db.select().from(gaTrafficSummaries).where(eq15(gaTrafficSummaries.projectId, projectId)).orderBy(desc7(gaTrafficSummaries.syncedAt)).limit(1).get();
11744
+ const allSnapshotRows = db.select().from(gaTrafficSnapshots).where(eq15(gaTrafficSnapshots.projectId, projectId)).all();
11474
11745
  if (!windowSummary && !fallbackSummary && allSnapshotRows.length === 0) return null;
11475
11746
  let snapshotMaxDate = "";
11476
11747
  for (const r of allSnapshotRows) if (r.date > snapshotMaxDate) snapshotMaxDate = r.date;
@@ -11529,7 +11800,7 @@ function buildGaSection(db, projectId, windowDays) {
11529
11800
  };
11530
11801
  }
11531
11802
  function buildSocialReferrals(db, projectId) {
11532
- const rows = db.select().from(gaSocialReferrals).where(eq14(gaSocialReferrals.projectId, projectId)).all();
11803
+ const rows = db.select().from(gaSocialReferrals).where(eq15(gaSocialReferrals.projectId, projectId)).all();
11533
11804
  if (rows.length === 0) return null;
11534
11805
  let total = 0;
11535
11806
  let organic = 0;
@@ -11561,7 +11832,7 @@ function buildSocialReferrals(db, projectId) {
11561
11832
  };
11562
11833
  }
11563
11834
  function buildAiReferrals(db, projectId) {
11564
- const rows = db.select().from(gaAiReferrals).where(eq14(gaAiReferrals.projectId, projectId)).all();
11835
+ const rows = db.select().from(gaAiReferrals).where(eq15(gaAiReferrals.projectId, projectId)).all();
11565
11836
  if (rows.length === 0) return null;
11566
11837
  const dimSessionsByTuple = /* @__PURE__ */ new Map();
11567
11838
  for (const r of rows) {
@@ -11618,7 +11889,7 @@ function buildAiReferrals(db, projectId) {
11618
11889
  return { totalSessions: total, totalUsers, bySource, trend, topLandingPages };
11619
11890
  }
11620
11891
  function nonSubresourceReferralPathCondition() {
11621
- return sql6`
11892
+ return sql7`
11622
11893
  LOWER(${aiReferralEventsHourly.landingPathNormalized}) NOT LIKE '/_next/static/%'
11623
11894
  AND LOWER(${aiReferralEventsHourly.landingPathNormalized}) NOT LIKE '/assets/%'
11624
11895
  AND LOWER(${aiReferralEventsHourly.landingPathNormalized}) NOT LIKE '/static/%'
@@ -11642,8 +11913,8 @@ function nonSubresourceReferralPathCondition() {
11642
11913
  }
11643
11914
  function buildServerActivity(db, projectId, windowDays) {
11644
11915
  const sourceRows = db.select({ id: trafficSources.id }).from(trafficSources).where(
11645
- and9(
11646
- eq14(trafficSources.projectId, projectId),
11916
+ and10(
11917
+ eq15(trafficSources.projectId, projectId),
11647
11918
  ne2(trafficSources.status, TrafficSourceStatuses.archived)
11648
11919
  )
11649
11920
  ).all();
@@ -11657,19 +11928,19 @@ function buildServerActivity(db, projectId, windowDays) {
11657
11928
  const priorStart = new Date(priorStartMs).toISOString();
11658
11929
  const trendStart = new Date(trendStartMs).toISOString();
11659
11930
  const sumVerifiedCrawlers = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
11660
- db.select({ total: sql6`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
11661
- and9(
11662
- eq14(crawlerEventsHourly.projectId, projectId),
11663
- eq14(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
11931
+ db.select({ total: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
11932
+ and10(
11933
+ eq15(crawlerEventsHourly.projectId, projectId),
11934
+ eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
11664
11935
  gte2(crawlerEventsHourly.tsHour, windowStartIso),
11665
11936
  exclusiveEnd ? lt(crawlerEventsHourly.tsHour, windowEndIso) : lte(crawlerEventsHourly.tsHour, windowEndIso)
11666
11937
  )
11667
11938
  ).get()?.total ?? 0
11668
11939
  );
11669
11940
  const sumUnverifiedCrawlers = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
11670
- db.select({ total: sql6`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
11671
- and9(
11672
- eq14(crawlerEventsHourly.projectId, projectId),
11941
+ db.select({ total: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
11942
+ and10(
11943
+ eq15(crawlerEventsHourly.projectId, projectId),
11673
11944
  ne2(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
11674
11945
  gte2(crawlerEventsHourly.tsHour, windowStartIso),
11675
11946
  exclusiveEnd ? lt(crawlerEventsHourly.tsHour, windowEndIso) : lte(crawlerEventsHourly.tsHour, windowEndIso)
@@ -11677,9 +11948,9 @@ function buildServerActivity(db, projectId, windowDays) {
11677
11948
  ).get()?.total ?? 0
11678
11949
  );
11679
11950
  const sumReferrals = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
11680
- db.select({ total: sql6`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
11681
- and9(
11682
- eq14(aiReferralEventsHourly.projectId, projectId),
11951
+ db.select({ total: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
11952
+ and10(
11953
+ eq15(aiReferralEventsHourly.projectId, projectId),
11683
11954
  nonSubresourceReferralPathCondition(),
11684
11955
  gte2(aiReferralEventsHourly.tsHour, windowStartIso),
11685
11956
  exclusiveEnd ? lt(aiReferralEventsHourly.tsHour, windowEndIso) : lte(aiReferralEventsHourly.tsHour, windowEndIso)
@@ -11687,9 +11958,9 @@ function buildServerActivity(db, projectId, windowDays) {
11687
11958
  ).get()?.total ?? 0
11688
11959
  );
11689
11960
  const sumUserFetches = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
11690
- db.select({ total: sql6`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
11691
- and9(
11692
- eq14(aiUserFetchEventsHourly.projectId, projectId),
11961
+ db.select({ total: sql7`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
11962
+ and10(
11963
+ eq15(aiUserFetchEventsHourly.projectId, projectId),
11693
11964
  gte2(aiUserFetchEventsHourly.tsHour, windowStartIso),
11694
11965
  exclusiveEnd ? lt(aiUserFetchEventsHourly.tsHour, windowEndIso) : lte(aiUserFetchEventsHourly.tsHour, windowEndIso)
11695
11966
  )
@@ -11706,31 +11977,31 @@ function buildServerActivity(db, projectId, windowDays) {
11706
11977
  const crawlerByOperatorRows = db.select({
11707
11978
  operator: crawlerEventsHourly.operator,
11708
11979
  verificationStatus: crawlerEventsHourly.verificationStatus,
11709
- hits: sql6`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
11980
+ hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
11710
11981
  }).from(crawlerEventsHourly).where(
11711
- and9(
11712
- eq14(crawlerEventsHourly.projectId, projectId),
11982
+ and10(
11983
+ eq15(crawlerEventsHourly.projectId, projectId),
11713
11984
  gte2(crawlerEventsHourly.tsHour, headlineStart),
11714
11985
  lte(crawlerEventsHourly.tsHour, headlineEnd)
11715
11986
  )
11716
11987
  ).groupBy(crawlerEventsHourly.operator, crawlerEventsHourly.verificationStatus).all();
11717
11988
  const crawlerByOperatorPriorRows = db.select({
11718
11989
  operator: crawlerEventsHourly.operator,
11719
- hits: sql6`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
11990
+ hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
11720
11991
  }).from(crawlerEventsHourly).where(
11721
- and9(
11722
- eq14(crawlerEventsHourly.projectId, projectId),
11723
- eq14(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
11992
+ and10(
11993
+ eq15(crawlerEventsHourly.projectId, projectId),
11994
+ eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
11724
11995
  gte2(crawlerEventsHourly.tsHour, priorStart),
11725
11996
  lt(crawlerEventsHourly.tsHour, headlineStart)
11726
11997
  )
11727
11998
  ).groupBy(crawlerEventsHourly.operator).all();
11728
11999
  const referralByOperatorRows = db.select({
11729
12000
  operator: aiReferralEventsHourly.operator,
11730
- hits: sql6`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
12001
+ hits: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
11731
12002
  }).from(aiReferralEventsHourly).where(
11732
- and9(
11733
- eq14(aiReferralEventsHourly.projectId, projectId),
12003
+ and10(
12004
+ eq15(aiReferralEventsHourly.projectId, projectId),
11734
12005
  nonSubresourceReferralPathCondition(),
11735
12006
  gte2(aiReferralEventsHourly.tsHour, headlineStart),
11736
12007
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
@@ -11738,10 +12009,10 @@ function buildServerActivity(db, projectId, windowDays) {
11738
12009
  ).groupBy(aiReferralEventsHourly.operator).all();
11739
12010
  const userFetchByOperatorRows = db.select({
11740
12011
  operator: aiUserFetchEventsHourly.operator,
11741
- hits: sql6`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
12012
+ hits: sql7`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
11742
12013
  }).from(aiUserFetchEventsHourly).where(
11743
- and9(
11744
- eq14(aiUserFetchEventsHourly.projectId, projectId),
12014
+ and10(
12015
+ eq15(aiUserFetchEventsHourly.projectId, projectId),
11745
12016
  gte2(aiUserFetchEventsHourly.tsHour, headlineStart),
11746
12017
  lte(aiUserFetchEventsHourly.tsHour, headlineEnd)
11747
12018
  )
@@ -11781,16 +12052,16 @@ function buildServerActivity(db, projectId, windowDays) {
11781
12052
  );
11782
12053
  const topPathsRows = db.select({
11783
12054
  path: crawlerEventsHourly.pathNormalized,
11784
- hits: sql6`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
11785
- operators: sql6`COUNT(DISTINCT ${crawlerEventsHourly.operator})`
12055
+ hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
12056
+ operators: sql7`COUNT(DISTINCT ${crawlerEventsHourly.operator})`
11786
12057
  }).from(crawlerEventsHourly).where(
11787
- and9(
11788
- eq14(crawlerEventsHourly.projectId, projectId),
11789
- eq14(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12058
+ and10(
12059
+ eq15(crawlerEventsHourly.projectId, projectId),
12060
+ eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
11790
12061
  gte2(crawlerEventsHourly.tsHour, headlineStart),
11791
12062
  lte(crawlerEventsHourly.tsHour, headlineEnd)
11792
12063
  )
11793
- ).groupBy(crawlerEventsHourly.pathNormalized).orderBy(desc7(sql6`SUM(${crawlerEventsHourly.hits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
12064
+ ).groupBy(crawlerEventsHourly.pathNormalized).orderBy(desc7(sql7`SUM(${crawlerEventsHourly.hits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
11794
12065
  const topCrawledPaths = topPathsRows.map((r) => ({
11795
12066
  path: r.path,
11796
12067
  verifiedHits: Number(r.hits),
@@ -11798,16 +12069,16 @@ function buildServerActivity(db, projectId, windowDays) {
11798
12069
  }));
11799
12070
  const referralProductsRows = db.select({
11800
12071
  product: aiReferralEventsHourly.product,
11801
- arrivals: sql6`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
11802
- landingPaths: sql6`COUNT(DISTINCT ${aiReferralEventsHourly.landingPathNormalized})`
12072
+ arrivals: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
12073
+ landingPaths: sql7`COUNT(DISTINCT ${aiReferralEventsHourly.landingPathNormalized})`
11803
12074
  }).from(aiReferralEventsHourly).where(
11804
- and9(
11805
- eq14(aiReferralEventsHourly.projectId, projectId),
12075
+ and10(
12076
+ eq15(aiReferralEventsHourly.projectId, projectId),
11806
12077
  nonSubresourceReferralPathCondition(),
11807
12078
  gte2(aiReferralEventsHourly.tsHour, headlineStart),
11808
12079
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
11809
12080
  )
11810
- ).groupBy(aiReferralEventsHourly.product).orderBy(desc7(sql6`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).all();
12081
+ ).groupBy(aiReferralEventsHourly.product).orderBy(desc7(sql7`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).all();
11811
12082
  const referralProducts = referralProductsRows.map((r) => ({
11812
12083
  product: r.product,
11813
12084
  arrivals: Number(r.arrivals),
@@ -11815,53 +12086,53 @@ function buildServerActivity(db, projectId, windowDays) {
11815
12086
  }));
11816
12087
  const topReferralRows = db.select({
11817
12088
  path: aiReferralEventsHourly.landingPathNormalized,
11818
- arrivals: sql6`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
11819
- products: sql6`COUNT(DISTINCT ${aiReferralEventsHourly.product})`
12089
+ arrivals: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
12090
+ products: sql7`COUNT(DISTINCT ${aiReferralEventsHourly.product})`
11820
12091
  }).from(aiReferralEventsHourly).where(
11821
- and9(
11822
- eq14(aiReferralEventsHourly.projectId, projectId),
12092
+ and10(
12093
+ eq15(aiReferralEventsHourly.projectId, projectId),
11823
12094
  nonSubresourceReferralPathCondition(),
11824
12095
  gte2(aiReferralEventsHourly.tsHour, headlineStart),
11825
12096
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
11826
12097
  )
11827
- ).groupBy(aiReferralEventsHourly.landingPathNormalized).orderBy(desc7(sql6`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
12098
+ ).groupBy(aiReferralEventsHourly.landingPathNormalized).orderBy(desc7(sql7`SUM(${aiReferralEventsHourly.sessionsOrHits})`)).limit(SERVER_ACTIVITY_TOP_PATHS_LIMIT).all();
11828
12099
  const topReferralLandingPaths = topReferralRows.map((r) => ({
11829
12100
  path: r.path,
11830
12101
  arrivals: Number(r.arrivals),
11831
12102
  distinctProducts: Number(r.products)
11832
12103
  }));
11833
12104
  const crawlerTrendRows = db.select({
11834
- date: sql6`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`,
11835
- hits: sql6`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
12105
+ date: sql7`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`,
12106
+ hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
11836
12107
  }).from(crawlerEventsHourly).where(
11837
- and9(
11838
- eq14(crawlerEventsHourly.projectId, projectId),
11839
- eq14(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12108
+ and10(
12109
+ eq15(crawlerEventsHourly.projectId, projectId),
12110
+ eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
11840
12111
  gte2(crawlerEventsHourly.tsHour, trendStart),
11841
12112
  lte(crawlerEventsHourly.tsHour, headlineEnd)
11842
12113
  )
11843
- ).groupBy(sql6`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`).all();
12114
+ ).groupBy(sql7`SUBSTR(${crawlerEventsHourly.tsHour}, 1, 10)`).all();
11844
12115
  const referralTrendRows = db.select({
11845
- date: sql6`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`,
11846
- hits: sql6`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
12116
+ date: sql7`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`,
12117
+ hits: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
11847
12118
  }).from(aiReferralEventsHourly).where(
11848
- and9(
11849
- eq14(aiReferralEventsHourly.projectId, projectId),
12119
+ and10(
12120
+ eq15(aiReferralEventsHourly.projectId, projectId),
11850
12121
  nonSubresourceReferralPathCondition(),
11851
12122
  gte2(aiReferralEventsHourly.tsHour, trendStart),
11852
12123
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
11853
12124
  )
11854
- ).groupBy(sql6`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`).all();
12125
+ ).groupBy(sql7`SUBSTR(${aiReferralEventsHourly.tsHour}, 1, 10)`).all();
11855
12126
  const userFetchTrendRows = db.select({
11856
- date: sql6`SUBSTR(${aiUserFetchEventsHourly.tsHour}, 1, 10)`,
11857
- hits: sql6`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
12127
+ date: sql7`SUBSTR(${aiUserFetchEventsHourly.tsHour}, 1, 10)`,
12128
+ hits: sql7`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
11858
12129
  }).from(aiUserFetchEventsHourly).where(
11859
- and9(
11860
- eq14(aiUserFetchEventsHourly.projectId, projectId),
12130
+ and10(
12131
+ eq15(aiUserFetchEventsHourly.projectId, projectId),
11861
12132
  gte2(aiUserFetchEventsHourly.tsHour, trendStart),
11862
12133
  lte(aiUserFetchEventsHourly.tsHour, headlineEnd)
11863
12134
  )
11864
- ).groupBy(sql6`SUBSTR(${aiUserFetchEventsHourly.tsHour}, 1, 10)`).all();
12135
+ ).groupBy(sql7`SUBSTR(${aiUserFetchEventsHourly.tsHour}, 1, 10)`).all();
11865
12136
  const emptyTrendEntry = () => ({ verifiedCrawlerHits: 0, userFetchHits: 0, referralArrivals: 0 });
11866
12137
  const dailyTrendMap = /* @__PURE__ */ new Map();
11867
12138
  for (const r of crawlerTrendRows) {
@@ -11912,7 +12183,7 @@ function buildServerActivity(db, projectId, windowDays) {
11912
12183
  };
11913
12184
  }
11914
12185
  function buildIndexingHealth(db, projectId) {
11915
- const gsc = db.select().from(gscCoverageSnapshots).where(eq14(gscCoverageSnapshots.projectId, projectId)).orderBy(desc7(gscCoverageSnapshots.date)).limit(1).get();
12186
+ const gsc = db.select().from(gscCoverageSnapshots).where(eq15(gscCoverageSnapshots.projectId, projectId)).orderBy(desc7(gscCoverageSnapshots.date)).limit(1).get();
11916
12187
  if (gsc) {
11917
12188
  const total = gsc.indexed + gsc.notIndexed;
11918
12189
  return {
@@ -11925,7 +12196,7 @@ function buildIndexingHealth(db, projectId) {
11925
12196
  indexedPct: total > 0 ? Math.round(gsc.indexed / total * 100) : 0
11926
12197
  };
11927
12198
  }
11928
- const bing = db.select().from(bingCoverageSnapshots).where(eq14(bingCoverageSnapshots.projectId, projectId)).orderBy(desc7(bingCoverageSnapshots.date)).limit(1).get();
12199
+ const bing = db.select().from(bingCoverageSnapshots).where(eq15(bingCoverageSnapshots.projectId, projectId)).orderBy(desc7(bingCoverageSnapshots.date)).limit(1).get();
11929
12200
  if (bing) {
11930
12201
  const total = bing.indexed + bing.notIndexed + bing.unknown;
11931
12202
  return {
@@ -11941,7 +12212,7 @@ function buildIndexingHealth(db, projectId) {
11941
12212
  return null;
11942
12213
  }
11943
12214
  function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
11944
- const visibilityRuns = db.select().from(runs).where(and9(eq14(runs.projectId, projectId), eq14(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter);
12215
+ const visibilityRuns = db.select().from(runs).where(and10(eq15(runs.projectId, projectId), eq15(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter);
11945
12216
  const totalQueries = queryLookup.byId.size;
11946
12217
  const points = [];
11947
12218
  for (const run of visibilityRuns) {
@@ -11989,15 +12260,15 @@ function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
11989
12260
  }
11990
12261
  function buildInsightList(db, projectId, locationFilter) {
11991
12262
  const recentRunIds = db.select({ id: runs.id, location: runs.location }).from(runs).where(
11992
- and9(
11993
- eq14(runs.projectId, projectId),
11994
- eq14(runs.kind, RunKinds["answer-visibility"]),
11995
- or3(eq14(runs.status, RunStatuses.completed), eq14(runs.status, RunStatuses.partial)),
12263
+ and10(
12264
+ eq15(runs.projectId, projectId),
12265
+ eq15(runs.kind, RunKinds["answer-visibility"]),
12266
+ or3(eq15(runs.status, RunStatuses.completed), eq15(runs.status, RunStatuses.partial)),
11996
12267
  notProbeRun()
11997
12268
  )
11998
12269
  ).orderBy(desc7(runs.createdAt)).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter).slice(0, INSIGHT_LOOKBACK_RUNS).map((r) => r.id);
11999
12270
  if (recentRunIds.length === 0) return [];
12000
- const rows = db.select().from(insights).where(and9(eq14(insights.projectId, projectId), inArray6(insights.runId, recentRunIds))).orderBy(desc7(insights.createdAt)).all();
12271
+ const rows = db.select().from(insights).where(and10(eq15(insights.projectId, projectId), inArray6(insights.runId, recentRunIds))).orderBy(desc7(insights.createdAt)).all();
12001
12272
  const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
12002
12273
  const flat = rows.filter((r) => !r.dismissed).map((r) => {
12003
12274
  const recommendation = r.recommendation;
@@ -12549,7 +12820,7 @@ function buildProjectReport(db, projectName, periodDays) {
12549
12820
  const project = resolveProject(db, projectName);
12550
12821
  const queryLookup = loadQueryLookup(db, project.id);
12551
12822
  const comparisonWindowDays = reportComparisonWindowDays(periodDays);
12552
- const allRuns = db.select().from(runs).where(and9(eq14(runs.projectId, project.id), notProbeRun())).orderBy(desc7(runs.createdAt), desc7(runs.id)).all();
12823
+ const allRuns = db.select().from(runs).where(and10(eq15(runs.projectId, project.id), notProbeRun())).orderBy(desc7(runs.createdAt), desc7(runs.id)).all();
12553
12824
  const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
12554
12825
  const completedVisRunGroups = groupRunsByCreatedAt(
12555
12826
  visibilityRuns.filter((r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial)
@@ -12558,7 +12829,7 @@ function buildProjectReport(db, projectName, periodDays) {
12558
12829
  const representativeLatestRun = pickGroupRepresentative(latestVisRunGroup) ?? visibilityRuns[0] ?? null;
12559
12830
  const latestSnapshots = loadSnapshotsForRunIds(db, latestVisRunGroup.map((r) => r.id));
12560
12831
  const latestRunLocation = representativeLatestRun?.location ?? null;
12561
- const competitorRows = db.select().from(competitors).where(eq14(competitors.projectId, project.id)).all();
12832
+ const competitorRows = db.select().from(competitors).where(eq15(competitors.projectId, project.id)).all();
12562
12833
  const competitorDomains = competitorRows.map((c) => c.domain);
12563
12834
  const ownedDomains = project.ownedDomains;
12564
12835
  const projectDomains = [project.canonicalDomain, ...ownedDomains];
@@ -12785,16 +13056,16 @@ async function reportRoutes(app) {
12785
13056
  }
12786
13057
 
12787
13058
  // ../api-routes/src/citations.ts
12788
- import { and as and10, eq as eq15, inArray as inArray7 } from "drizzle-orm";
13059
+ import { and as and11, eq as eq16, inArray as inArray7 } from "drizzle-orm";
12789
13060
  async function citationRoutes(app) {
12790
13061
  app.get("/projects/:name/citations/visibility", async (request, reply) => {
12791
13062
  const project = resolveProject(app.db, request.params.name);
12792
13063
  const configuredProviders = project.providers;
12793
- const projectQueries = app.db.select().from(queries).where(eq15(queries.projectId, project.id)).all();
13064
+ const projectQueries = app.db.select().from(queries).where(eq16(queries.projectId, project.id)).all();
12794
13065
  if (projectQueries.length === 0) {
12795
13066
  return reply.send(emptyCitationVisibility("no-queries"));
12796
13067
  }
12797
- const projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and10(eq15(runs.projectId, project.id), notProbeRun())).all();
13068
+ const projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and11(eq16(runs.projectId, project.id), notProbeRun())).all();
12798
13069
  if (projectRuns.length === 0) {
12799
13070
  return reply.send(emptyCitationVisibility("no-runs-yet"));
12800
13071
  }
@@ -12818,7 +13089,7 @@ async function citationRoutes(app) {
12818
13089
  queryId: s.queryId,
12819
13090
  runCreatedAt: runCreatedAt.get(s.runId) ?? s.createdAt
12820
13091
  }));
12821
- const projectCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq15(competitors.projectId, project.id)).all().map((c) => normalizeDomain2(c.domain)).filter((d) => d.length > 0);
13092
+ const projectCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq16(competitors.projectId, project.id)).all().map((c) => normalizeDomain2(c.domain)).filter((d) => d.length > 0);
12822
13093
  const response = computeCitationVisibility({
12823
13094
  queries: projectQueries.map((q) => ({ id: q.id, query: q.query })),
12824
13095
  snapshots,
@@ -12950,7 +13221,7 @@ function normalizeDomain2(domain) {
12950
13221
  }
12951
13222
 
12952
13223
  // ../api-routes/src/visibility-stats.ts
12953
- import { and as and11, desc as desc8, eq as eq16, inArray as inArray8 } from "drizzle-orm";
13224
+ import { and as and12, desc as desc8, eq as eq17, inArray as inArray8 } from "drizzle-orm";
12954
13225
  function round42(value) {
12955
13226
  return Math.round(value * 1e4) / 1e4;
12956
13227
  }
@@ -13070,8 +13341,8 @@ async function visibilityStatsRoutes(app) {
13070
13341
  if (!Number.isInteger(n) || n <= 0) throw validationError('"lastRuns" must be a positive integer');
13071
13342
  lastRuns = n;
13072
13343
  }
13073
- const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq16(queries.projectId, project.id)).all();
13074
- let projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(and11(eq16(runs.projectId, project.id), eq16(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc8(runs.createdAt)).all().filter((r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial);
13344
+ const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq17(queries.projectId, project.id)).all();
13345
+ let projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(and12(eq17(runs.projectId, project.id), eq17(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc8(runs.createdAt)).all().filter((r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial);
13075
13346
  if (sinceMs !== null) projectRuns = projectRuns.filter((r) => Date.parse(r.createdAt) >= sinceMs);
13076
13347
  if (untilMs !== null) projectRuns = projectRuns.filter((r) => Date.parse(r.createdAt) <= untilMs);
13077
13348
  if (lastRuns !== null) projectRuns = projectRuns.slice(0, lastRuns);
@@ -13107,7 +13378,7 @@ async function visibilityStatsRoutes(app) {
13107
13378
  }
13108
13379
 
13109
13380
  // ../api-routes/src/composites.ts
13110
- import { eq as eq17, and as and12, desc as desc9, sql as sql7, like as like2, or as or4, inArray as inArray9 } from "drizzle-orm";
13381
+ import { eq as eq18, and as and13, desc as desc9, sql as sql8, like as like2, or as or4, inArray as inArray9 } from "drizzle-orm";
13111
13382
  var TOP_INSIGHT_LIMIT = 5;
13112
13383
  var SEARCH_HIT_HARD_LIMIT = 50;
13113
13384
  var SEARCH_SNIPPET_RADIUS = 80;
@@ -13125,7 +13396,7 @@ async function compositeRoutes(app) {
13125
13396
  const project = resolveProject(app.db, request.params.name);
13126
13397
  const filterLocation = (request.query.location ?? "").trim() || null;
13127
13398
  const sinceIso = parseSinceFilter(request.query.since);
13128
- const allRunsRaw = app.db.select().from(runs).where(and12(eq17(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
13399
+ const allRunsRaw = app.db.select().from(runs).where(and13(eq18(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
13129
13400
  const allRuns = allRunsRaw.filter((r) => runMatchesFilters(r, filterLocation, sinceIso));
13130
13401
  const totalRuns = allRuns.length;
13131
13402
  const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
@@ -13138,15 +13409,15 @@ async function compositeRoutes(app) {
13138
13409
  const previousVisibilityRun = pickGroupRepresentative(previousVisRunGroup);
13139
13410
  const latestRunRow = allRuns[0] ?? null;
13140
13411
  const latestRun = latestRunRow ? { totalRuns, run: summarizeRun(latestRunRow) } : { totalRuns: 0, run: null };
13141
- const healthRow = app.db.select().from(healthSnapshots).where(eq17(healthSnapshots.projectId, project.id)).orderBy(desc9(healthSnapshots.createdAt)).limit(1).get();
13412
+ const healthRow = app.db.select().from(healthSnapshots).where(eq18(healthSnapshots.projectId, project.id)).orderBy(desc9(healthSnapshots.createdAt)).limit(1).get();
13142
13413
  const health = healthRow ? mapHealthRow2(healthRow) : null;
13143
- const insightRows = app.db.select().from(insights).where(eq17(insights.projectId, project.id)).orderBy(desc9(insights.createdAt)).all();
13414
+ const insightRows = app.db.select().from(insights).where(eq18(insights.projectId, project.id)).orderBy(desc9(insights.createdAt)).all();
13144
13415
  const topInsights = insightRows.filter((row) => !row.dismissed).slice(0, TOP_INSIGHT_LIMIT).map(mapInsightRow2);
13145
13416
  const sparklineRunIds = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => r.id);
13146
13417
  const snapshotRunIds = new Set(sparklineRunIds);
13147
13418
  for (const run of latestVisRunGroup) snapshotRunIds.add(run.id);
13148
13419
  for (const run of previousVisRunGroup) snapshotRunIds.add(run.id);
13149
- const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq17(queries.projectId, project.id)).all();
13420
+ const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq18(queries.projectId, project.id)).all();
13150
13421
  const queryIdByText = new Map(projectQueries.map((q) => [normalizeQueryText(q.query), q.id]));
13151
13422
  const snapshotsByRun = loadSnapshotsByRunIds(app, [...snapshotRunIds], queryIdByText);
13152
13423
  const latestSnapshots = latestVisRunGroup.flatMap((r) => snapshotsByRun.get(r.id) ?? []);
@@ -13162,7 +13433,7 @@ async function compositeRoutes(app) {
13162
13433
  trackedPrevious,
13163
13434
  previousVisibilityRun?.createdAt ?? null
13164
13435
  );
13165
- const competitorRows = app.db.select().from(competitors).where(eq17(competitors.projectId, project.id)).all();
13436
+ const competitorRows = app.db.select().from(competitors).where(eq18(competitors.projectId, project.id)).all();
13166
13437
  const queryLookup = { byId: new Map(projectQueries.map((q) => [q.id, q.query])) };
13167
13438
  for (const snapshots of snapshotsByRun.values()) {
13168
13439
  for (const snapshot of snapshots) {
@@ -13280,25 +13551,25 @@ async function compositeRoutes(app) {
13280
13551
  citedDomains: querySnapshots.citedDomains,
13281
13552
  rawResponse: querySnapshots.rawResponse,
13282
13553
  createdAt: querySnapshots.createdAt
13283
- }).from(querySnapshots).innerJoin(queries, eq17(querySnapshots.queryId, queries.id)).where(
13284
- and12(
13285
- eq17(queries.projectId, project.id),
13554
+ }).from(querySnapshots).innerJoin(queries, eq18(querySnapshots.queryId, queries.id)).where(
13555
+ and13(
13556
+ eq18(queries.projectId, project.id),
13286
13557
  or4(
13287
- sql7`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
13288
- sql7`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
13289
- sql7`${querySnapshots.rawResponse} LIKE ${pattern} ESCAPE '\\'`,
13558
+ sql8`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
13559
+ sql8`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
13560
+ sql8`${querySnapshots.rawResponse} LIKE ${pattern} ESCAPE '\\'`,
13290
13561
  like2(queries.query, pattern)
13291
13562
  )
13292
13563
  )
13293
13564
  ).orderBy(desc9(querySnapshots.createdAt)).limit(limit + 1).all());
13294
13565
  const insightMatches = app.db.select().from(insights).where(
13295
- and12(
13296
- eq17(insights.projectId, project.id),
13566
+ and13(
13567
+ eq18(insights.projectId, project.id),
13297
13568
  or4(
13298
13569
  like2(insights.title, pattern),
13299
13570
  like2(insights.query, pattern),
13300
- sql7`${insights.recommendation} LIKE ${pattern} ESCAPE '\\'`,
13301
- sql7`${insights.cause} LIKE ${pattern} ESCAPE '\\'`
13571
+ sql8`${insights.recommendation} LIKE ${pattern} ESCAPE '\\'`,
13572
+ sql8`${insights.cause} LIKE ${pattern} ESCAPE '\\'`
13302
13573
  )
13303
13574
  )
13304
13575
  ).orderBy(desc9(insights.createdAt)).limit(limit + 1).all();
@@ -13503,17 +13774,17 @@ function buildSuggestedQueriesFromGsc(app, projectId, trackedQueries) {
13503
13774
  const cutoff = new Date(Date.now() - 28 * 24 * 60 * 60 * 1e3).toISOString().slice(0, 10);
13504
13775
  const rows = app.db.select({
13505
13776
  query: gscSearchData.query,
13506
- impressions: sql7`COALESCE(SUM(${gscSearchData.impressions}), 0)`,
13507
- clicks: sql7`COALESCE(SUM(${gscSearchData.clicks}), 0)`,
13777
+ impressions: sql8`COALESCE(SUM(${gscSearchData.impressions}), 0)`,
13778
+ clicks: sql8`COALESCE(SUM(${gscSearchData.clicks}), 0)`,
13508
13779
  // Weighted average: SUM(position * impressions) / SUM(impressions).
13509
13780
  // NULLIF guards the degenerate impressions=0 case (SQLite returns NULL,
13510
13781
  // which the JS coerces to 0 — caught by the impression floor anyway).
13511
- avgPosition: sql7`COALESCE(SUM(${gscSearchData.position} * ${gscSearchData.impressions}) * 1.0 / NULLIF(SUM(${gscSearchData.impressions}), 0), 0)`
13512
- }).from(gscSearchData).where(and12(
13513
- eq17(gscSearchData.projectId, projectId),
13514
- sql7`${gscSearchData.date} >= ${cutoff}`,
13515
- sql7`${gscSearchData.impressions} > 0`
13516
- )).groupBy(gscSearchData.query).orderBy(sql7`SUM(${gscSearchData.impressions}) DESC`).limit(100).all();
13782
+ avgPosition: sql8`COALESCE(SUM(${gscSearchData.position} * ${gscSearchData.impressions}) * 1.0 / NULLIF(SUM(${gscSearchData.impressions}), 0), 0)`
13783
+ }).from(gscSearchData).where(and13(
13784
+ eq18(gscSearchData.projectId, projectId),
13785
+ sql8`${gscSearchData.date} >= ${cutoff}`,
13786
+ sql8`${gscSearchData.impressions} > 0`
13787
+ )).groupBy(gscSearchData.query).orderBy(sql8`SUM(${gscSearchData.impressions}) DESC`).limit(100).all();
13517
13788
  const gscRows = rows.map((r) => ({
13518
13789
  query: r.query,
13519
13790
  impressions: Number(r.impressions),
@@ -13533,8 +13804,8 @@ function buildIndexCoverageScore(app, projectId) {
13533
13804
  tooltip,
13534
13805
  trend: []
13535
13806
  };
13536
- const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq17(gscCoverageSnapshots.projectId, projectId)).orderBy(desc9(gscCoverageSnapshots.date)).limit(1).get();
13537
- const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq17(bingCoverageSnapshots.projectId, projectId)).orderBy(desc9(bingCoverageSnapshots.date)).limit(1).get();
13807
+ const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq18(gscCoverageSnapshots.projectId, projectId)).orderBy(desc9(gscCoverageSnapshots.date)).limit(1).get();
13808
+ const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq18(bingCoverageSnapshots.projectId, projectId)).orderBy(desc9(bingCoverageSnapshots.date)).limit(1).get();
13538
13809
  const chosen = pickIndexCoverageRow(gscRow, bingRow);
13539
13810
  if (!chosen) return empty;
13540
13811
  const total = chosen.indexed + chosen.notIndexed;
@@ -13560,7 +13831,7 @@ function countGoogleDeindexedUrls(app, projectId) {
13560
13831
  url: gscUrlInspections.url,
13561
13832
  indexingState: gscUrlInspections.indexingState,
13562
13833
  inspectedAt: gscUrlInspections.inspectedAt
13563
- }).from(gscUrlInspections).where(eq17(gscUrlInspections.projectId, projectId)).orderBy(desc9(gscUrlInspections.inspectedAt)).all();
13834
+ }).from(gscUrlInspections).where(eq18(gscUrlInspections.projectId, projectId)).orderBy(desc9(gscUrlInspections.inspectedAt)).all();
13564
13835
  if (rows.length === 0) return 0;
13565
13836
  const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
13566
13837
  const historyByUrl = /* @__PURE__ */ new Map();
@@ -17992,7 +18263,7 @@ var canonryLocalRouteCatalog = [
17992
18263
  method: "post",
17993
18264
  path: "/api/v1/projects/{name}/agent/prompt",
17994
18265
  summary: "Send a prompt to Aero and stream events back as SSE",
17995
- description: 'Posts a prompt into the project\'s Aero session and streams `AgentEvent` frames as `text/event-stream`. Each frame is `data: <JSON>\\n\\n`. The server brackets the stream with `{"type":"stream_open"}` and `{"type":"stream_close"}` control frames; `{"type":"error","message":"..."}` surfaces in-stream failures without collapsing the stream. Returns 409 `AGENT_BUSY` if another turn is already in flight for this project. Body field `scope` accepts "all" | "read-only"; omitted defaults to "read-only" (safe dashboard surface). The CLI passes "all" to keep write tools available.',
18266
+ description: 'Posts a prompt into the project\'s Aero session and streams `AgentEvent` frames as `text/event-stream`. Each frame is `data: <JSON>\\n\\n`. The server brackets the stream with `{"type":"stream_open"}` and `{"type":"stream_close"}` control frames; `{"type":"error","message":"..."}` surfaces in-stream failures without collapsing the stream. Returns 409 `AGENT_BUSY` if another turn is already in flight for this project. Body field `scope` accepts "all" | "read-only"; omitted defaults to "read-only" (safe dashboard surface). Body field `profile` accepts "default" | "ads-operator"; omitted keeps the default full Canonry operator surface. The CLI passes "all" to keep write tools available.',
17996
18267
  tags: ["agent"],
17997
18268
  parameters: [nameParameter],
17998
18269
  requestBody: {
@@ -18017,6 +18288,11 @@ var canonryLocalRouteCatalog = [
18017
18288
  type: "string",
18018
18289
  enum: ["all", "read-only"],
18019
18290
  description: 'Tool surface scope. Default "read-only". Set "all" to enable write tools.'
18291
+ },
18292
+ profile: {
18293
+ type: "string",
18294
+ enum: ["default", "ads-operator"],
18295
+ description: 'Tool profile. Default "default". Set "ads-operator" to use the narrower ads SaaS operator surface plus the ads context tool.'
18020
18296
  }
18021
18297
  }
18022
18298
  }
@@ -18202,7 +18478,7 @@ async function settingsRoutes(app, opts) {
18202
18478
 
18203
18479
  // ../api-routes/src/keys.ts
18204
18480
  import crypto12 from "crypto";
18205
- import { desc as desc10, eq as eq18 } from "drizzle-orm";
18481
+ import { desc as desc10, eq as eq19 } from "drizzle-orm";
18206
18482
  var KEYS_WRITE_SCOPE = "keys.write";
18207
18483
  function toApiKeyDto(row) {
18208
18484
  const scopes = Array.isArray(row.scopes) ? row.scopes : [];
@@ -18211,6 +18487,7 @@ function toApiKeyDto(row) {
18211
18487
  name: row.name,
18212
18488
  keyPrefix: row.keyPrefix,
18213
18489
  scopes,
18490
+ projectId: row.projectId ?? null,
18214
18491
  readOnly: isReadOnlyKey(scopes),
18215
18492
  createdAt: row.createdAt,
18216
18493
  lastUsedAt: row.lastUsedAt ?? null,
@@ -18218,8 +18495,9 @@ function toApiKeyDto(row) {
18218
18495
  };
18219
18496
  }
18220
18497
  async function keysRoutes(app) {
18221
- app.get("/keys", async () => {
18222
- const rows = app.db.select().from(apiKeys).orderBy(desc10(apiKeys.createdAt)).all();
18498
+ app.get("/keys", async (request) => {
18499
+ const scopedProjectId = request.apiKey?.projectId;
18500
+ const rows = scopedProjectId ? app.db.select().from(apiKeys).where(eq19(apiKeys.projectId, scopedProjectId)).orderBy(desc10(apiKeys.createdAt)).all() : app.db.select().from(apiKeys).orderBy(desc10(apiKeys.createdAt)).all();
18223
18501
  return { keys: rows.map(toApiKeyDto) };
18224
18502
  });
18225
18503
  app.get("/keys/self", async (request) => {
@@ -18227,7 +18505,7 @@ async function keysRoutes(app) {
18227
18505
  if (!id) {
18228
18506
  throw notFound("API key", "self");
18229
18507
  }
18230
- const row = app.db.select().from(apiKeys).where(eq18(apiKeys.id, id)).get();
18508
+ const row = app.db.select().from(apiKeys).where(eq19(apiKeys.id, id)).get();
18231
18509
  if (!row) {
18232
18510
  throw notFound("API key", id);
18233
18511
  }
@@ -18239,7 +18517,17 @@ async function keysRoutes(app) {
18239
18517
  if (!parsed.success) {
18240
18518
  throw validationError("Invalid API key request", { issues: parsed.error.issues });
18241
18519
  }
18242
- const { name, scopes } = parsed.data;
18520
+ const { name, scopes, projectId } = parsed.data;
18521
+ const requesterProjectId = request.apiKey?.projectId;
18522
+ if (requesterProjectId && projectId !== requesterProjectId) {
18523
+ throw forbidden("A project-scoped key can only mint keys scoped to its own project.");
18524
+ }
18525
+ if (projectId) {
18526
+ const proj = app.db.select({ id: projects.id }).from(projects).where(eq19(projects.id, projectId)).get();
18527
+ if (!proj) {
18528
+ throw notFound("Project", projectId);
18529
+ }
18530
+ }
18243
18531
  const raw = `cnry_${crypto12.randomBytes(16).toString("hex")}`;
18244
18532
  const keyHash = hashApiKey(raw);
18245
18533
  const keyPrefix = raw.slice(0, 9);
@@ -18253,6 +18541,7 @@ async function keysRoutes(app) {
18253
18541
  keyHash,
18254
18542
  keyPrefix,
18255
18543
  scopes: effectiveScopes,
18544
+ projectId: projectId ?? null,
18256
18545
  createdAt: now
18257
18546
  }).run();
18258
18547
  writeAuditLog(tx, auditFromRequest(request, {
@@ -18260,7 +18549,7 @@ async function keysRoutes(app) {
18260
18549
  action: "api-key.created",
18261
18550
  entityType: "api-key",
18262
18551
  entityId: id,
18263
- diff: { name, keyPrefix, scopes: effectiveScopes }
18552
+ diff: { name, keyPrefix, scopes: effectiveScopes, projectId: projectId ?? null }
18264
18553
  }));
18265
18554
  });
18266
18555
  const dto = {
@@ -18268,6 +18557,7 @@ async function keysRoutes(app) {
18268
18557
  name,
18269
18558
  keyPrefix,
18270
18559
  scopes: effectiveScopes,
18560
+ projectId: projectId ?? null,
18271
18561
  readOnly: isReadOnlyKey(effectiveScopes),
18272
18562
  createdAt: now,
18273
18563
  lastUsedAt: null,
@@ -18279,19 +18569,23 @@ async function keysRoutes(app) {
18279
18569
  app.post("/keys/:id/revoke", async (request) => {
18280
18570
  requireScope(request, KEYS_WRITE_SCOPE);
18281
18571
  const { id } = request.params;
18282
- const row = app.db.select().from(apiKeys).where(eq18(apiKeys.id, id)).get();
18572
+ const row = app.db.select().from(apiKeys).where(eq19(apiKeys.id, id)).get();
18283
18573
  if (!row) {
18284
18574
  throw notFound("API key", id);
18285
18575
  }
18286
18576
  if (request.apiKey?.id === id) {
18287
18577
  throw validationError("Cannot revoke the API key you are currently authenticating with");
18288
18578
  }
18579
+ const requesterProjectId = request.apiKey?.projectId;
18580
+ if (requesterProjectId && row.projectId !== requesterProjectId) {
18581
+ throw forbidden("A project-scoped key can only revoke keys scoped to its own project.");
18582
+ }
18289
18583
  if (row.revokedAt) {
18290
18584
  return toApiKeyDto(row);
18291
18585
  }
18292
18586
  const now = (/* @__PURE__ */ new Date()).toISOString();
18293
18587
  app.db.transaction((tx) => {
18294
- tx.update(apiKeys).set({ revokedAt: now }).where(eq18(apiKeys.id, id)).run();
18588
+ tx.update(apiKeys).set({ revokedAt: now }).where(eq19(apiKeys.id, id)).run();
18295
18589
  writeAuditLog(tx, auditFromRequest(request, {
18296
18590
  actor: "api",
18297
18591
  action: "api-key.revoked",
@@ -18364,7 +18658,7 @@ async function telemetryRoutes(app, opts) {
18364
18658
 
18365
18659
  // ../api-routes/src/schedules.ts
18366
18660
  import crypto13 from "crypto";
18367
- import { and as and13, eq as eq19 } from "drizzle-orm";
18661
+ import { and as and14, eq as eq20 } from "drizzle-orm";
18368
18662
  function parseKindParam(raw) {
18369
18663
  if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
18370
18664
  const parsed = schedulableRunKindSchema.safeParse(raw);
@@ -18391,7 +18685,7 @@ async function scheduleRoutes(app, opts) {
18391
18685
  if (!sourceId) {
18392
18686
  throw validationError('"sourceId" is required when kind is "traffic-sync"');
18393
18687
  }
18394
- const sourceRow = app.db.select().from(trafficSources).where(eq19(trafficSources.id, sourceId)).get();
18688
+ const sourceRow = app.db.select().from(trafficSources).where(eq20(trafficSources.id, sourceId)).get();
18395
18689
  if (!sourceRow || sourceRow.projectId !== project.id) {
18396
18690
  throw notFound("Traffic source", sourceId);
18397
18691
  }
@@ -18436,7 +18730,7 @@ async function scheduleRoutes(app, opts) {
18436
18730
  }
18437
18731
  const now = (/* @__PURE__ */ new Date()).toISOString();
18438
18732
  const enabledBool = enabled !== false;
18439
- const existing = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
18733
+ const existing = app.db.select().from(schedules).where(and14(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
18440
18734
  if (existing) {
18441
18735
  app.db.update(schedules).set({
18442
18736
  cronExpr,
@@ -18446,7 +18740,7 @@ async function scheduleRoutes(app, opts) {
18446
18740
  sourceId: sourceId ?? null,
18447
18741
  enabled: enabledBool,
18448
18742
  updatedAt: now
18449
- }).where(eq19(schedules.id, existing.id)).run();
18743
+ }).where(eq20(schedules.id, existing.id)).run();
18450
18744
  } else {
18451
18745
  app.db.insert(schedules).values({
18452
18746
  id: crypto13.randomUUID(),
@@ -18470,13 +18764,13 @@ async function scheduleRoutes(app, opts) {
18470
18764
  diff: { kind, cronExpr, preset, timezone, providers, sourceId }
18471
18765
  });
18472
18766
  opts.onScheduleUpdated?.("upsert", project.id, kind);
18473
- const schedule = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
18767
+ const schedule = app.db.select().from(schedules).where(and14(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
18474
18768
  return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
18475
18769
  });
18476
18770
  app.get("/projects/:name/schedule", async (request, reply) => {
18477
18771
  const project = resolveProject(app.db, request.params.name);
18478
18772
  const kind = parseKindParam(request.query?.kind);
18479
- const schedule = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
18773
+ const schedule = app.db.select().from(schedules).where(and14(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
18480
18774
  if (!schedule) {
18481
18775
  throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
18482
18776
  }
@@ -18485,11 +18779,11 @@ async function scheduleRoutes(app, opts) {
18485
18779
  app.delete("/projects/:name/schedule", async (request, reply) => {
18486
18780
  const project = resolveProject(app.db, request.params.name);
18487
18781
  const kind = parseKindParam(request.query?.kind);
18488
- const schedule = app.db.select().from(schedules).where(and13(eq19(schedules.projectId, project.id), eq19(schedules.kind, kind))).get();
18782
+ const schedule = app.db.select().from(schedules).where(and14(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
18489
18783
  if (!schedule) {
18490
18784
  throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
18491
18785
  }
18492
- app.db.delete(schedules).where(eq19(schedules.id, schedule.id)).run();
18786
+ app.db.delete(schedules).where(eq20(schedules.id, schedule.id)).run();
18493
18787
  writeAuditLog(app.db, {
18494
18788
  projectId: project.id,
18495
18789
  actor: "api",
@@ -18522,7 +18816,7 @@ function formatSchedule(row) {
18522
18816
 
18523
18817
  // ../api-routes/src/notifications.ts
18524
18818
  import crypto14 from "crypto";
18525
- import { eq as eq20 } from "drizzle-orm";
18819
+ import { eq as eq21 } from "drizzle-orm";
18526
18820
  var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed", "insight.critical", "insight.high"];
18527
18821
  async function notificationRoutes(app, opts = {}) {
18528
18822
  const allowLoopback = opts.allowLoopbackWebhooks === true;
@@ -18562,22 +18856,22 @@ async function notificationRoutes(app, opts = {}) {
18562
18856
  diff: { channel, ...redactNotificationUrl(url), events }
18563
18857
  });
18564
18858
  return reply.status(201).send({
18565
- ...formatNotification(app.db.select().from(notifications).where(eq20(notifications.id, id)).get()),
18859
+ ...formatNotification(app.db.select().from(notifications).where(eq21(notifications.id, id)).get()),
18566
18860
  webhookSecret
18567
18861
  });
18568
18862
  });
18569
18863
  app.get("/projects/:name/notifications", async (request, reply) => {
18570
18864
  const project = resolveProject(app.db, request.params.name);
18571
- const rows = app.db.select().from(notifications).where(eq20(notifications.projectId, project.id)).all();
18865
+ const rows = app.db.select().from(notifications).where(eq21(notifications.projectId, project.id)).all();
18572
18866
  return reply.send(rows.map(formatNotification));
18573
18867
  });
18574
18868
  app.delete("/projects/:name/notifications/:id", async (request, reply) => {
18575
18869
  const project = resolveProject(app.db, request.params.name);
18576
- const notification = app.db.select().from(notifications).where(eq20(notifications.id, request.params.id)).get();
18870
+ const notification = app.db.select().from(notifications).where(eq21(notifications.id, request.params.id)).get();
18577
18871
  if (!notification || notification.projectId !== project.id) {
18578
18872
  throw notFound("Notification", request.params.id);
18579
18873
  }
18580
- app.db.delete(notifications).where(eq20(notifications.id, notification.id)).run();
18874
+ app.db.delete(notifications).where(eq21(notifications.id, notification.id)).run();
18581
18875
  writeAuditLog(app.db, {
18582
18876
  projectId: project.id,
18583
18877
  actor: "api",
@@ -18589,7 +18883,7 @@ async function notificationRoutes(app, opts = {}) {
18589
18883
  });
18590
18884
  app.post("/projects/:name/notifications/:id/test", async (request, reply) => {
18591
18885
  const project = resolveProject(app.db, request.params.name);
18592
- const notification = app.db.select().from(notifications).where(eq20(notifications.id, request.params.id)).get();
18886
+ const notification = app.db.select().from(notifications).where(eq21(notifications.id, request.params.id)).get();
18593
18887
  if (!notification || notification.projectId !== project.id) {
18594
18888
  throw notFound("Notification", request.params.id);
18595
18889
  }
@@ -18642,7 +18936,7 @@ function formatNotification(row) {
18642
18936
 
18643
18937
  // ../api-routes/src/google.ts
18644
18938
  import crypto18 from "crypto";
18645
- import { eq as eq21, and as and14, desc as desc11, sql as sql8, inArray as inArray10 } from "drizzle-orm";
18939
+ import { eq as eq22, and as and15, desc as desc11, sql as sql9, inArray as inArray10 } from "drizzle-orm";
18646
18940
 
18647
18941
  // ../api-routes/src/gbp-summary.ts
18648
18942
  function computeMetricTotals(rows) {
@@ -20475,7 +20769,7 @@ async function googleRoutes(app, opts) {
20475
20769
  if (!projectId) {
20476
20770
  return reply.status(400).send("Stale OAuth state \u2014 restart the connect flow.");
20477
20771
  }
20478
- const project = app.db.select().from(projects).where(eq21(projects.id, projectId)).get();
20772
+ const project = app.db.select().from(projects).where(eq22(projects.id, projectId)).get();
20479
20773
  if (!project) {
20480
20774
  return reply.status(400).send("Project no longer exists. Restart the connect flow.");
20481
20775
  }
@@ -20607,22 +20901,22 @@ async function googleRoutes(app, opts) {
20607
20901
  if (opts.onGscSyncRequested) {
20608
20902
  opts.onGscSyncRequested(runId, project.id, { days, full });
20609
20903
  }
20610
- const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
20904
+ const run = app.db.select().from(runs).where(eq22(runs.id, runId)).get();
20611
20905
  return run;
20612
20906
  });
20613
20907
  app.get("/projects/:name/google/gsc/performance", async (request) => {
20614
20908
  const project = resolveProject(app.db, request.params.name);
20615
20909
  const { startDate, endDate, query, page, limit, offset } = request.query;
20616
20910
  const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
20617
- const conditions = [eq21(gscSearchData.projectId, project.id)];
20618
- if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
20619
- else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
20620
- if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
20621
- if (query) conditions.push(sql8`${gscSearchData.query} LIKE ${"%" + escapeLikePattern(query) + "%"} ESCAPE '\\'`);
20622
- if (page) conditions.push(sql8`${gscSearchData.page} LIKE ${"%" + escapeLikePattern(page) + "%"} ESCAPE '\\'`);
20911
+ const conditions = [eq22(gscSearchData.projectId, project.id)];
20912
+ if (startDate) conditions.push(sql9`${gscSearchData.date} >= ${startDate}`);
20913
+ else if (cutoffDate) conditions.push(sql9`${gscSearchData.date} >= ${cutoffDate}`);
20914
+ if (endDate) conditions.push(sql9`${gscSearchData.date} <= ${endDate}`);
20915
+ if (query) conditions.push(sql9`${gscSearchData.query} LIKE ${"%" + escapeLikePattern(query) + "%"} ESCAPE '\\'`);
20916
+ if (page) conditions.push(sql9`${gscSearchData.page} LIKE ${"%" + escapeLikePattern(page) + "%"} ESCAPE '\\'`);
20623
20917
  const limitVal = Math.max(parseInt(limit ?? "500", 10) || 0, 1);
20624
20918
  const offsetVal = Math.max(parseInt(offset ?? "0", 10) || 0, 0);
20625
- const rows = app.db.select().from(gscSearchData).where(and14(...conditions)).orderBy(desc11(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
20919
+ const rows = app.db.select().from(gscSearchData).where(and15(...conditions)).orderBy(desc11(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
20626
20920
  return rows.map((r) => ({
20627
20921
  date: r.date,
20628
20922
  query: r.query,
@@ -20639,20 +20933,31 @@ async function googleRoutes(app, opts) {
20639
20933
  const project = resolveProject(app.db, request.params.name);
20640
20934
  const { startDate, endDate } = request.query;
20641
20935
  const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
20642
- const conditions = [eq21(gscSearchData.projectId, project.id)];
20643
- if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
20644
- else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
20645
- if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
20646
- const rows = app.db.select({
20936
+ const windowStart = startDate ?? cutoffDate ?? "";
20937
+ const windowEnd = endDate ?? "9999-12-31";
20938
+ const dailyTotals = readGscDailyTotals(app.db, project.id, windowStart, windowEnd);
20939
+ const conditions = [eq22(gscSearchData.projectId, project.id)];
20940
+ if (startDate) conditions.push(sql9`${gscSearchData.date} >= ${startDate}`);
20941
+ else if (cutoffDate) conditions.push(sql9`${gscSearchData.date} >= ${cutoffDate}`);
20942
+ if (endDate) conditions.push(sql9`${gscSearchData.date} <= ${endDate}`);
20943
+ const dimensionedRows = app.db.select({
20647
20944
  date: gscSearchData.date,
20648
- clicks: sql8`COALESCE(SUM(${gscSearchData.clicks}), 0)`,
20649
- impressions: sql8`COALESCE(SUM(${gscSearchData.impressions}), 0)`
20650
- }).from(gscSearchData).where(and14(...conditions)).groupBy(gscSearchData.date).orderBy(gscSearchData.date).all();
20651
- const daily = rows.map((r) => ({
20652
- date: r.date,
20653
- clicks: r.clicks,
20654
- impressions: r.impressions,
20655
- ctr: r.impressions > 0 ? r.clicks / r.impressions : 0
20945
+ clicks: sql9`COALESCE(SUM(${gscSearchData.clicks}), 0)`,
20946
+ impressions: sql9`COALESCE(SUM(${gscSearchData.impressions}), 0)`
20947
+ }).from(gscSearchData).where(and15(...conditions)).groupBy(gscSearchData.date).orderBy(gscSearchData.date).all();
20948
+ const daily = mergeGscDailyTotalsWithFallback(
20949
+ dailyTotals,
20950
+ dimensionedRows.map((r) => ({
20951
+ date: r.date,
20952
+ clicks: r.clicks,
20953
+ impressions: r.impressions,
20954
+ position: 0
20955
+ }))
20956
+ ).map((d) => ({
20957
+ date: d.date,
20958
+ clicks: d.clicks,
20959
+ impressions: d.impressions,
20960
+ ctr: d.impressions > 0 ? d.clicks / d.impressions : 0
20656
20961
  }));
20657
20962
  const totalClicks = daily.reduce((sum, d) => sum + d.clicks, 0);
20658
20963
  const totalImpressions = daily.reduce((sum, d) => sum + d.impressions, 0);
@@ -20730,9 +21035,9 @@ async function googleRoutes(app, opts) {
20730
21035
  app.get("/projects/:name/google/gsc/inspections", async (request) => {
20731
21036
  const project = resolveProject(app.db, request.params.name);
20732
21037
  const { url, limit } = request.query;
20733
- const conditions = [eq21(gscUrlInspections.projectId, project.id)];
20734
- if (url) conditions.push(eq21(gscUrlInspections.url, url));
20735
- const rows = app.db.select().from(gscUrlInspections).where(and14(...conditions)).orderBy(desc11(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
21038
+ const conditions = [eq22(gscUrlInspections.projectId, project.id)];
21039
+ if (url) conditions.push(eq22(gscUrlInspections.url, url));
21040
+ const rows = app.db.select().from(gscUrlInspections).where(and15(...conditions)).orderBy(desc11(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
20736
21041
  return rows.map((r) => ({
20737
21042
  id: r.id,
20738
21043
  url: r.url,
@@ -20751,7 +21056,7 @@ async function googleRoutes(app, opts) {
20751
21056
  });
20752
21057
  app.get("/projects/:name/google/gsc/deindexed", async (request) => {
20753
21058
  const project = resolveProject(app.db, request.params.name);
20754
- const allInspections = app.db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
21059
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq22(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
20755
21060
  const byUrl = /* @__PURE__ */ new Map();
20756
21061
  for (const row of allInspections) {
20757
21062
  const existing = byUrl.get(row.url);
@@ -20779,7 +21084,7 @@ async function googleRoutes(app, opts) {
20779
21084
  });
20780
21085
  app.get("/projects/:name/google/gsc/coverage", async (request) => {
20781
21086
  const project = resolveProject(app.db, request.params.name);
20782
- const allInspections = app.db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
21087
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq22(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
20783
21088
  const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
20784
21089
  const latestByUrl = /* @__PURE__ */ new Map();
20785
21090
  const historyByUrl = /* @__PURE__ */ new Map();
@@ -20828,7 +21133,7 @@ async function googleRoutes(app, opts) {
20828
21133
  const total = latestByUrl.size;
20829
21134
  const indexed = indexedUrls.length;
20830
21135
  const notIndexed = notIndexedUrls.length;
20831
- const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq21(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.createdAt)).limit(1).get();
21136
+ const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq22(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.createdAt)).limit(1).get();
20832
21137
  const lastSyncedAt = latestSnapshot?.createdAt ?? null;
20833
21138
  const formatRow = (r) => ({
20834
21139
  id: r.id,
@@ -20879,7 +21184,7 @@ async function googleRoutes(app, opts) {
20879
21184
  const project = resolveProject(app.db, request.params.name);
20880
21185
  const parsed = parseInt(request.query.limit ?? "90", 10);
20881
21186
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
20882
- const rows = app.db.select().from(gscCoverageSnapshots).where(eq21(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.date)).limit(limit).all();
21187
+ const rows = app.db.select().from(gscCoverageSnapshots).where(eq22(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.date)).limit(limit).all();
20883
21188
  return rows.map((r) => ({
20884
21189
  date: r.date,
20885
21190
  indexed: r.indexed,
@@ -20948,7 +21253,7 @@ async function googleRoutes(app, opts) {
20948
21253
  if (opts.onInspectSitemapRequested) {
20949
21254
  opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl });
20950
21255
  }
20951
- const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
21256
+ const run = app.db.select().from(runs).where(eq22(runs.id, runId)).get();
20952
21257
  return { sitemaps, primarySitemapUrl: sitemapUrl, run };
20953
21258
  });
20954
21259
  app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
@@ -20975,7 +21280,7 @@ async function googleRoutes(app, opts) {
20975
21280
  if (opts.onInspectSitemapRequested) {
20976
21281
  opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
20977
21282
  }
20978
- const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
21283
+ const run = app.db.select().from(runs).where(eq22(runs.id, runId)).get();
20979
21284
  return run;
20980
21285
  });
20981
21286
  app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
@@ -21022,7 +21327,7 @@ async function googleRoutes(app, opts) {
21022
21327
  const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
21023
21328
  let urlsToNotify = request.body?.urls ?? [];
21024
21329
  if (request.body?.allUnindexed) {
21025
- const allInspections = app.db.select().from(gscUrlInspections).where(eq21(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
21330
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq22(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
21026
21331
  const latestByUrl = /* @__PURE__ */ new Map();
21027
21332
  for (const row of allInspections) {
21028
21333
  if (!latestByUrl.has(row.url)) {
@@ -21140,7 +21445,7 @@ async function googleRoutes(app, opts) {
21140
21445
  };
21141
21446
  }
21142
21447
  function listSelectionResponse(projectId) {
21143
- const rows = app.db.select().from(gbpLocations).where(eq21(gbpLocations.projectId, projectId)).all();
21448
+ const rows = app.db.select().from(gbpLocations).where(eq22(gbpLocations.projectId, projectId)).all();
21144
21449
  const dtos = rows.map(rowToDto2);
21145
21450
  return {
21146
21451
  locations: dtos,
@@ -21149,15 +21454,15 @@ async function googleRoutes(app, opts) {
21149
21454
  };
21150
21455
  }
21151
21456
  function clearGbpProjectData(tx, projectId) {
21152
- tx.delete(gbpDailyMetrics).where(eq21(gbpDailyMetrics.projectId, projectId)).run();
21153
- tx.delete(gbpKeywordImpressions).where(eq21(gbpKeywordImpressions.projectId, projectId)).run();
21154
- tx.delete(gbpKeywordMonthly).where(eq21(gbpKeywordMonthly.projectId, projectId)).run();
21155
- tx.delete(gbpPlaceActions).where(eq21(gbpPlaceActions.projectId, projectId)).run();
21156
- tx.delete(gbpLodgingSnapshots).where(eq21(gbpLodgingSnapshots.projectId, projectId)).run();
21157
- tx.delete(gbpLocations).where(eq21(gbpLocations.projectId, projectId)).run();
21457
+ tx.delete(gbpDailyMetrics).where(eq22(gbpDailyMetrics.projectId, projectId)).run();
21458
+ tx.delete(gbpKeywordImpressions).where(eq22(gbpKeywordImpressions.projectId, projectId)).run();
21459
+ tx.delete(gbpKeywordMonthly).where(eq22(gbpKeywordMonthly.projectId, projectId)).run();
21460
+ tx.delete(gbpPlaceActions).where(eq22(gbpPlaceActions.projectId, projectId)).run();
21461
+ tx.delete(gbpLodgingSnapshots).where(eq22(gbpLodgingSnapshots.projectId, projectId)).run();
21462
+ tx.delete(gbpLocations).where(eq22(gbpLocations.projectId, projectId)).run();
21158
21463
  }
21159
21464
  function currentProjectAccount(projectId) {
21160
- const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq21(gbpLocations.projectId, projectId)).limit(1).get();
21465
+ const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq22(gbpLocations.projectId, projectId)).limit(1).get();
21161
21466
  return row?.accountName ?? null;
21162
21467
  }
21163
21468
  app.post("/projects/:name/gbp/locations/discover", async (request) => {
@@ -21227,7 +21532,7 @@ async function googleRoutes(app, opts) {
21227
21532
  app.db.transaction((tx) => {
21228
21533
  if (switching) clearGbpProjectData(tx, project.id);
21229
21534
  for (const remote of remoteLocations) {
21230
- const existing = tx.select().from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), eq21(gbpLocations.locationName, remote.name))).get();
21535
+ const existing = tx.select().from(gbpLocations).where(and15(eq22(gbpLocations.projectId, project.id), eq22(gbpLocations.locationName, remote.name))).get();
21231
21536
  const profile = buildLocationProfileFields(remote);
21232
21537
  if (existing) {
21233
21538
  tx.update(gbpLocations).set({
@@ -21240,7 +21545,7 @@ async function googleRoutes(app, opts) {
21240
21545
  mapsUri: remote.metadata?.mapsUri ?? null,
21241
21546
  ...profile,
21242
21547
  updatedAt: now
21243
- }).where(eq21(gbpLocations.id, existing.id)).run();
21548
+ }).where(eq22(gbpLocations.id, existing.id)).run();
21244
21549
  } else {
21245
21550
  tx.insert(gbpLocations).values({
21246
21551
  id: crypto18.randomUUID(),
@@ -21324,11 +21629,11 @@ async function googleRoutes(app, opts) {
21324
21629
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid selection request");
21325
21630
  }
21326
21631
  const { selected } = parsed.data;
21327
- const existing = app.db.select().from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), eq21(gbpLocations.locationName, locationName))).get();
21632
+ const existing = app.db.select().from(gbpLocations).where(and15(eq22(gbpLocations.projectId, project.id), eq22(gbpLocations.locationName, locationName))).get();
21328
21633
  if (!existing) throw notFound("GBP location", locationName);
21329
21634
  const now = (/* @__PURE__ */ new Date()).toISOString();
21330
21635
  app.db.transaction((tx) => {
21331
- tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq21(gbpLocations.id, existing.id)).run();
21636
+ tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq22(gbpLocations.id, existing.id)).run();
21332
21637
  writeAuditLog(tx, {
21333
21638
  projectId: project.id,
21334
21639
  actor: "api",
@@ -21337,7 +21642,7 @@ async function googleRoutes(app, opts) {
21337
21642
  entityId: locationName
21338
21643
  });
21339
21644
  });
21340
- const refreshed = app.db.select().from(gbpLocations).where(eq21(gbpLocations.id, existing.id)).get();
21645
+ const refreshed = app.db.select().from(gbpLocations).where(eq22(gbpLocations.id, existing.id)).get();
21341
21646
  return rowToDto2(refreshed);
21342
21647
  });
21343
21648
  app.delete("/projects/:name/gbp/connection", async (request, reply) => {
@@ -21381,10 +21686,10 @@ async function googleRoutes(app, opts) {
21381
21686
  });
21382
21687
  app.get("/projects/:name/gbp/metrics", async (request) => {
21383
21688
  const project = resolveProject(app.db, request.params.name);
21384
- const conditions = [eq21(gbpDailyMetrics.projectId, project.id)];
21385
- if (request.query.locationName) conditions.push(eq21(gbpDailyMetrics.locationName, request.query.locationName));
21386
- if (request.query.metric) conditions.push(eq21(gbpDailyMetrics.metric, request.query.metric));
21387
- const rows = app.db.select().from(gbpDailyMetrics).where(and14(...conditions)).orderBy(desc11(gbpDailyMetrics.date)).all();
21689
+ const conditions = [eq22(gbpDailyMetrics.projectId, project.id)];
21690
+ if (request.query.locationName) conditions.push(eq22(gbpDailyMetrics.locationName, request.query.locationName));
21691
+ if (request.query.metric) conditions.push(eq22(gbpDailyMetrics.metric, request.query.metric));
21692
+ const rows = app.db.select().from(gbpDailyMetrics).where(and15(...conditions)).orderBy(desc11(gbpDailyMetrics.date)).all();
21388
21693
  return {
21389
21694
  metrics: rows.map((r) => ({ locationName: r.locationName, date: r.date, metric: r.metric, value: r.value })),
21390
21695
  total: rows.length
@@ -21392,9 +21697,9 @@ async function googleRoutes(app, opts) {
21392
21697
  });
21393
21698
  app.get("/projects/:name/gbp/keywords", async (request) => {
21394
21699
  const project = resolveProject(app.db, request.params.name);
21395
- const conditions = [eq21(gbpKeywordImpressions.projectId, project.id)];
21396
- if (request.query.locationName) conditions.push(eq21(gbpKeywordImpressions.locationName, request.query.locationName));
21397
- const rows = app.db.select().from(gbpKeywordImpressions).where(and14(...conditions)).all();
21700
+ const conditions = [eq22(gbpKeywordImpressions.projectId, project.id)];
21701
+ if (request.query.locationName) conditions.push(eq22(gbpKeywordImpressions.locationName, request.query.locationName));
21702
+ const rows = app.db.select().from(gbpKeywordImpressions).where(and15(...conditions)).all();
21398
21703
  rows.sort((a, b) => (b.valueCount ?? -1) - (a.valueCount ?? -1));
21399
21704
  const thresholded = rows.filter((r) => r.valueThreshold !== null).length;
21400
21705
  return {
@@ -21412,9 +21717,9 @@ async function googleRoutes(app, opts) {
21412
21717
  });
21413
21718
  app.get("/projects/:name/gbp/place-actions", async (request) => {
21414
21719
  const project = resolveProject(app.db, request.params.name);
21415
- const conditions = [eq21(gbpPlaceActions.projectId, project.id)];
21416
- if (request.query.locationName) conditions.push(eq21(gbpPlaceActions.locationName, request.query.locationName));
21417
- const rows = app.db.select().from(gbpPlaceActions).where(and14(...conditions)).all();
21720
+ const conditions = [eq22(gbpPlaceActions.projectId, project.id)];
21721
+ if (request.query.locationName) conditions.push(eq22(gbpPlaceActions.locationName, request.query.locationName));
21722
+ const rows = app.db.select().from(gbpPlaceActions).where(and15(...conditions)).all();
21418
21723
  return {
21419
21724
  placeActions: rows.map((r) => ({
21420
21725
  locationName: r.locationName,
@@ -21429,9 +21734,9 @@ async function googleRoutes(app, opts) {
21429
21734
  });
21430
21735
  app.get("/projects/:name/gbp/lodging", async (request) => {
21431
21736
  const project = resolveProject(app.db, request.params.name);
21432
- const conditions = [eq21(gbpLodgingSnapshots.projectId, project.id)];
21433
- if (request.query.locationName) conditions.push(eq21(gbpLodgingSnapshots.locationName, request.query.locationName));
21434
- const rows = app.db.select().from(gbpLodgingSnapshots).where(and14(...conditions)).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
21737
+ const conditions = [eq22(gbpLodgingSnapshots.projectId, project.id)];
21738
+ if (request.query.locationName) conditions.push(eq22(gbpLodgingSnapshots.locationName, request.query.locationName));
21739
+ const rows = app.db.select().from(gbpLodgingSnapshots).where(and15(...conditions)).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
21435
21740
  const latestByLocation = /* @__PURE__ */ new Map();
21436
21741
  for (const row of rows) {
21437
21742
  if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
@@ -21446,9 +21751,9 @@ async function googleRoutes(app, opts) {
21446
21751
  });
21447
21752
  app.get("/projects/:name/gbp/attributes", async (request) => {
21448
21753
  const project = resolveProject(app.db, request.params.name);
21449
- const conditions = [eq21(gbpAttributesSnapshots.projectId, project.id)];
21450
- if (request.query.locationName) conditions.push(eq21(gbpAttributesSnapshots.locationName, request.query.locationName));
21451
- const rows = app.db.select().from(gbpAttributesSnapshots).where(and14(...conditions)).orderBy(desc11(gbpAttributesSnapshots.syncedAt)).all();
21754
+ const conditions = [eq22(gbpAttributesSnapshots.projectId, project.id)];
21755
+ if (request.query.locationName) conditions.push(eq22(gbpAttributesSnapshots.locationName, request.query.locationName));
21756
+ const rows = app.db.select().from(gbpAttributesSnapshots).where(and15(...conditions)).orderBy(desc11(gbpAttributesSnapshots.syncedAt)).all();
21452
21757
  const latestByLocation = /* @__PURE__ */ new Map();
21453
21758
  for (const row of rows) {
21454
21759
  if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
@@ -21466,9 +21771,9 @@ async function googleRoutes(app, opts) {
21466
21771
  });
21467
21772
  app.get("/projects/:name/gbp/places", async (request) => {
21468
21773
  const project = resolveProject(app.db, request.params.name);
21469
- const conditions = [eq21(gbpPlaceDetails.projectId, project.id)];
21470
- if (request.query.locationName) conditions.push(eq21(gbpPlaceDetails.locationName, request.query.locationName));
21471
- const rows = app.db.select().from(gbpPlaceDetails).where(and14(...conditions)).orderBy(desc11(gbpPlaceDetails.syncedAt)).all();
21774
+ const conditions = [eq22(gbpPlaceDetails.projectId, project.id)];
21775
+ if (request.query.locationName) conditions.push(eq22(gbpPlaceDetails.locationName, request.query.locationName));
21776
+ const rows = app.db.select().from(gbpPlaceDetails).where(and15(...conditions)).orderBy(desc11(gbpPlaceDetails.syncedAt)).all();
21472
21777
  const latestByLocation = /* @__PURE__ */ new Map();
21473
21778
  for (const row of rows) {
21474
21779
  if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
@@ -21487,7 +21792,7 @@ async function googleRoutes(app, opts) {
21487
21792
  app.get("/projects/:name/gbp/summary", async (request) => {
21488
21793
  const project = resolveProject(app.db, request.params.name);
21489
21794
  const locationName = request.query.locationName ?? null;
21490
- const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), eq21(gbpLocations.selected, true))).all().map((r) => r.n);
21795
+ const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and15(eq22(gbpLocations.projectId, project.id), eq22(gbpLocations.selected, true))).all().map((r) => r.n);
21491
21796
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
21492
21797
  if (locationNames.length === 0) {
21493
21798
  return buildGbpSummary({
@@ -21501,10 +21806,10 @@ async function googleRoutes(app, opts) {
21501
21806
  locationProfiles: []
21502
21807
  });
21503
21808
  }
21504
- const metricRows = app.db.select().from(gbpDailyMetrics).where(and14(eq21(gbpDailyMetrics.projectId, project.id), inArray10(gbpDailyMetrics.locationName, locationNames))).all();
21505
- const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and14(eq21(gbpKeywordImpressions.projectId, project.id), inArray10(gbpKeywordImpressions.locationName, locationNames))).all();
21506
- const placeActionRows = app.db.select().from(gbpPlaceActions).where(and14(eq21(gbpPlaceActions.projectId, project.id), inArray10(gbpPlaceActions.locationName, locationNames))).all();
21507
- const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and14(eq21(gbpLodgingSnapshots.projectId, project.id), inArray10(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
21809
+ const metricRows = app.db.select().from(gbpDailyMetrics).where(and15(eq22(gbpDailyMetrics.projectId, project.id), inArray10(gbpDailyMetrics.locationName, locationNames))).all();
21810
+ const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and15(eq22(gbpKeywordImpressions.projectId, project.id), inArray10(gbpKeywordImpressions.locationName, locationNames))).all();
21811
+ const placeActionRows = app.db.select().from(gbpPlaceActions).where(and15(eq22(gbpPlaceActions.projectId, project.id), inArray10(gbpPlaceActions.locationName, locationNames))).all();
21812
+ const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and15(eq22(gbpLodgingSnapshots.projectId, project.id), inArray10(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
21508
21813
  const latestLodgingByLocation = /* @__PURE__ */ new Map();
21509
21814
  for (const row of lodgingRows) {
21510
21815
  if (!latestLodgingByLocation.has(row.locationName)) {
@@ -21518,7 +21823,7 @@ async function googleRoutes(app, opts) {
21518
21823
  regularHours: gbpLocations.regularHours,
21519
21824
  primaryPhone: gbpLocations.primaryPhone,
21520
21825
  openStatus: gbpLocations.openStatus
21521
- }).from(gbpLocations).where(and14(eq21(gbpLocations.projectId, project.id), inArray10(gbpLocations.locationName, locationNames))).all();
21826
+ }).from(gbpLocations).where(and15(eq22(gbpLocations.projectId, project.id), inArray10(gbpLocations.locationName, locationNames))).all();
21522
21827
  return buildGbpSummary({
21523
21828
  locationName,
21524
21829
  locationCount: locationNames.length,
@@ -21534,7 +21839,7 @@ async function googleRoutes(app, opts) {
21534
21839
 
21535
21840
  // ../api-routes/src/ads.ts
21536
21841
  import crypto19 from "crypto";
21537
- import { eq as eq22, and as and15, asc as asc2, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
21842
+ import { eq as eq23, and as and16, asc as asc3, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
21538
21843
  function statusDto(row) {
21539
21844
  if (!row) return { connected: false };
21540
21845
  return {
@@ -21584,7 +21889,7 @@ async function adsRoutes(app, opts) {
21584
21889
  createdAt: existingCfg?.createdAt ?? now,
21585
21890
  updatedAt: now
21586
21891
  });
21587
- const existingRow = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
21892
+ const existingRow = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
21588
21893
  app.db.transaction((tx) => {
21589
21894
  if (existingRow) {
21590
21895
  tx.update(adsConnections).set({
@@ -21594,7 +21899,7 @@ async function adsRoutes(app, opts) {
21594
21899
  timezone: account.timezone,
21595
21900
  status: account.status,
21596
21901
  updatedAt: now
21597
- }).where(eq22(adsConnections.id, existingRow.id)).run();
21902
+ }).where(eq23(adsConnections.id, existingRow.id)).run();
21598
21903
  } else {
21599
21904
  tx.insert(adsConnections).values({
21600
21905
  id: crypto19.randomUUID(),
@@ -21616,16 +21921,16 @@ async function adsRoutes(app, opts) {
21616
21921
  entityId: account.id
21617
21922
  }));
21618
21923
  });
21619
- const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
21924
+ const row = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
21620
21925
  return statusDto(row);
21621
21926
  }
21622
21927
  );
21623
21928
  app.delete("/projects/:name/ads/connection", async (request) => {
21624
21929
  const project = resolveProject(app.db, request.params.name);
21625
- const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
21930
+ const row = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
21626
21931
  if (row) {
21627
21932
  app.db.transaction((tx) => {
21628
- tx.delete(adsConnections).where(eq22(adsConnections.id, row.id)).run();
21933
+ tx.delete(adsConnections).where(eq23(adsConnections.id, row.id)).run();
21629
21934
  writeAuditLog(tx, auditFromRequest(request, {
21630
21935
  projectId: project.id,
21631
21936
  actor: "api",
@@ -21641,18 +21946,18 @@ async function adsRoutes(app, opts) {
21641
21946
  });
21642
21947
  app.get("/projects/:name/ads/status", async (request) => {
21643
21948
  const project = resolveProject(app.db, request.params.name);
21644
- const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
21949
+ const row = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
21645
21950
  return statusDto(row);
21646
21951
  });
21647
21952
  app.post("/projects/:name/ads/sync", async (request) => {
21648
21953
  const project = resolveProject(app.db, request.params.name);
21649
- const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
21954
+ const row = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
21650
21955
  if (!row) {
21651
21956
  throw validationError('No ads connection for this project. Run "canonry ads connect" first.');
21652
21957
  }
21653
- const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and15(
21654
- eq22(runs.projectId, project.id),
21655
- eq22(runs.kind, RunKinds["ads-sync"]),
21958
+ const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and16(
21959
+ eq23(runs.projectId, project.id),
21960
+ eq23(runs.kind, RunKinds["ads-sync"]),
21656
21961
  inArray11(runs.status, [RunStatuses.queued, RunStatuses.running])
21657
21962
  )).get();
21658
21963
  if (inFlight) {
@@ -21674,9 +21979,9 @@ async function adsRoutes(app, opts) {
21674
21979
  });
21675
21980
  app.get("/projects/:name/ads/campaigns", async (request) => {
21676
21981
  const project = resolveProject(app.db, request.params.name);
21677
- const campaignRows = app.db.select().from(adsCampaigns).where(eq22(adsCampaigns.projectId, project.id)).all();
21678
- const groupRows = app.db.select().from(adsAdGroups).where(eq22(adsAdGroups.projectId, project.id)).all();
21679
- const adRows = app.db.select().from(adsAds).where(eq22(adsAds.projectId, project.id)).all();
21982
+ const campaignRows = app.db.select().from(adsCampaigns).where(eq23(adsCampaigns.projectId, project.id)).all();
21983
+ const groupRows = app.db.select().from(adsAdGroups).where(eq23(adsAdGroups.projectId, project.id)).all();
21984
+ const adRows = app.db.select().from(adsAds).where(eq23(adsAds.projectId, project.id)).all();
21680
21985
  const adsByGroup = /* @__PURE__ */ new Map();
21681
21986
  for (const ad of adRows) {
21682
21987
  const dto = {
@@ -21730,12 +22035,12 @@ async function adsRoutes(app, opts) {
21730
22035
  }
21731
22036
  parsedLevel = result.data;
21732
22037
  }
21733
- const conditions = [eq22(adsInsightsDaily.projectId, project.id)];
21734
- if (parsedLevel) conditions.push(eq22(adsInsightsDaily.level, parsedLevel));
21735
- if (entityId) conditions.push(eq22(adsInsightsDaily.entityId, entityId));
22038
+ const conditions = [eq23(adsInsightsDaily.projectId, project.id)];
22039
+ if (parsedLevel) conditions.push(eq23(adsInsightsDaily.level, parsedLevel));
22040
+ if (entityId) conditions.push(eq23(adsInsightsDaily.entityId, entityId));
21736
22041
  if (from) conditions.push(gte3(adsInsightsDaily.date, from));
21737
22042
  if (to) conditions.push(lte2(adsInsightsDaily.date, to));
21738
- const rows = app.db.select().from(adsInsightsDaily).where(and15(...conditions)).orderBy(asc2(adsInsightsDaily.date)).all();
22043
+ const rows = app.db.select().from(adsInsightsDaily).where(and16(...conditions)).orderBy(asc3(adsInsightsDaily.date)).all();
21739
22044
  const dtoRows = rows.map((row) => ({
21740
22045
  level: row.level,
21741
22046
  entityId: row.entityId,
@@ -21747,19 +22052,19 @@ async function adsRoutes(app, opts) {
21747
22052
  ctr: adsCtr(row.clicks, row.impressions),
21748
22053
  cpcMicros: adsCpcMicros(row.spendMicros, row.clicks)
21749
22054
  }));
21750
- const conn = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
22055
+ const conn = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
21751
22056
  const response = { rows: dtoRows, currencyCode: conn?.currencyCode ?? null };
21752
22057
  return response;
21753
22058
  });
21754
22059
  app.get("/projects/:name/ads/summary", async (request) => {
21755
22060
  const project = resolveProject(app.db, request.params.name);
21756
- const row = app.db.select().from(adsConnections).where(eq22(adsConnections.projectId, project.id)).get();
21757
- const campaignCount = app.db.select().from(adsCampaigns).where(eq22(adsCampaigns.projectId, project.id)).all().length;
21758
- const adGroupCount = app.db.select().from(adsAdGroups).where(eq22(adsAdGroups.projectId, project.id)).all().length;
21759
- const adCount = app.db.select().from(adsAds).where(eq22(adsAds.projectId, project.id)).all().length;
21760
- const campaignInsights = app.db.select().from(adsInsightsDaily).where(and15(
21761
- eq22(adsInsightsDaily.projectId, project.id),
21762
- eq22(adsInsightsDaily.level, "campaign")
22061
+ const row = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
22062
+ const campaignCount = app.db.select().from(adsCampaigns).where(eq23(adsCampaigns.projectId, project.id)).all().length;
22063
+ const adGroupCount = app.db.select().from(adsAdGroups).where(eq23(adsAdGroups.projectId, project.id)).all().length;
22064
+ const adCount = app.db.select().from(adsAds).where(eq23(adsAds.projectId, project.id)).all().length;
22065
+ const campaignInsights = app.db.select().from(adsInsightsDaily).where(and16(
22066
+ eq23(adsInsightsDaily.projectId, project.id),
22067
+ eq23(adsInsightsDaily.level, "campaign")
21763
22068
  )).all();
21764
22069
  let impressions = 0;
21765
22070
  let clicks = 0;
@@ -21799,7 +22104,7 @@ async function adsRoutes(app, opts) {
21799
22104
 
21800
22105
  // ../api-routes/src/bing.ts
21801
22106
  import crypto20 from "crypto";
21802
- import { eq as eq23, and as and16, desc as desc12 } from "drizzle-orm";
22107
+ import { eq as eq24, and as and17, desc as desc12 } from "drizzle-orm";
21803
22108
 
21804
22109
  // ../integration-bing/src/constants.ts
21805
22110
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
@@ -22173,7 +22478,7 @@ async function bingRoutes(app, opts) {
22173
22478
  const store = requireConnectionStore();
22174
22479
  const project = resolveProject(app.db, request.params.name);
22175
22480
  requireConnection(store, project.canonicalDomain);
22176
- const allInspections = app.db.select().from(bingUrlInspections).where(eq23(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
22481
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq24(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
22177
22482
  const latestByUrl = /* @__PURE__ */ new Map();
22178
22483
  const definitiveByUrl = /* @__PURE__ */ new Map();
22179
22484
  for (const row of allInspections) {
@@ -22262,7 +22567,7 @@ async function bingRoutes(app, opts) {
22262
22567
  const project = resolveProject(app.db, request.params.name);
22263
22568
  const parsed = parseInt(request.query.limit ?? "90", 10);
22264
22569
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
22265
- const rows = app.db.select().from(bingCoverageSnapshots).where(eq23(bingCoverageSnapshots.projectId, project.id)).orderBy(desc12(bingCoverageSnapshots.date)).limit(limit).all();
22570
+ const rows = app.db.select().from(bingCoverageSnapshots).where(eq24(bingCoverageSnapshots.projectId, project.id)).orderBy(desc12(bingCoverageSnapshots.date)).limit(limit).all();
22266
22571
  return rows.map((r) => ({
22267
22572
  date: r.date,
22268
22573
  indexed: r.indexed,
@@ -22274,7 +22579,7 @@ async function bingRoutes(app, opts) {
22274
22579
  requireConnectionStore();
22275
22580
  const project = resolveProject(app.db, request.params.name);
22276
22581
  const { url, limit } = request.query;
22277
- const whereClause = url ? and16(eq23(bingUrlInspections.projectId, project.id), eq23(bingUrlInspections.url, url)) : eq23(bingUrlInspections.projectId, project.id);
22582
+ const whereClause = url ? and17(eq24(bingUrlInspections.projectId, project.id), eq24(bingUrlInspections.url, url)) : eq24(bingUrlInspections.projectId, project.id);
22278
22583
  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();
22279
22584
  return filtered.map((r) => ({
22280
22585
  id: r.id,
@@ -22364,7 +22669,7 @@ async function bingRoutes(app, opts) {
22364
22669
  anchorCount: result.AnchorCount ?? null,
22365
22670
  discoveryDate
22366
22671
  }).run();
22367
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq23(runs.id, runId)).run();
22672
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq24(runs.id, runId)).run();
22368
22673
  return {
22369
22674
  id,
22370
22675
  url,
@@ -22380,7 +22685,7 @@ async function bingRoutes(app, opts) {
22380
22685
  } catch (e) {
22381
22686
  const msg = e instanceof Error ? e.message : String(e);
22382
22687
  bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
22383
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(runs.id, runId)).run();
22688
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq24(runs.id, runId)).run();
22384
22689
  throw e;
22385
22690
  }
22386
22691
  });
@@ -22407,7 +22712,7 @@ async function bingRoutes(app, opts) {
22407
22712
  } else {
22408
22713
  bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
22409
22714
  }
22410
- const run = app.db.select().from(runs).where(eq23(runs.id, runId)).get();
22715
+ const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
22411
22716
  return run;
22412
22717
  });
22413
22718
  app.post("/projects/:name/bing/request-indexing", async (request) => {
@@ -22419,7 +22724,7 @@ async function bingRoutes(app, opts) {
22419
22724
  }
22420
22725
  let urlsToSubmit = request.body?.urls ?? [];
22421
22726
  if (request.body?.allUnindexed) {
22422
- const allInspections = app.db.select().from(bingUrlInspections).where(eq23(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
22727
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq24(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
22423
22728
  const latestByUrl = /* @__PURE__ */ new Map();
22424
22729
  for (const row of allInspections) {
22425
22730
  if (!latestByUrl.has(row.url)) {
@@ -22506,18 +22811,19 @@ async function bingRoutes(app, opts) {
22506
22811
  import fs from "fs";
22507
22812
  import path from "path";
22508
22813
  import os from "os";
22509
- import { eq as eq24, and as and17 } from "drizzle-orm";
22814
+ import { eq as eq25, and as and18 } from "drizzle-orm";
22510
22815
  function getScreenshotDir() {
22511
22816
  return path.join(os.homedir(), ".canonry", "screenshots");
22512
22817
  }
22513
22818
  async function cdpRoutes(app, opts) {
22514
22819
  app.get("/screenshots/:snapshotId", async (request, reply) => {
22515
22820
  const { snapshotId } = request.params;
22516
- const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(eq24(querySnapshots.id, snapshotId)).get();
22821
+ const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath, projectId: runs.projectId }).from(querySnapshots).innerJoin(runs, eq25(querySnapshots.runId, runs.id)).where(eq25(querySnapshots.id, snapshotId)).get();
22517
22822
  if (!snapshot?.screenshotPath) {
22518
22823
  const err = notFound("Screenshot", snapshotId);
22519
22824
  return reply.code(err.statusCode).send(err.toJSON());
22520
22825
  }
22826
+ assertProjectScope(request, snapshot.projectId);
22521
22827
  const base = path.resolve(getScreenshotDir());
22522
22828
  const fullPath = path.resolve(path.join(base, snapshot.screenshotPath));
22523
22829
  if (!fullPath.startsWith(base + path.sep) && fullPath !== base) {
@@ -22580,7 +22886,7 @@ async function cdpRoutes(app, opts) {
22580
22886
  async (request, reply) => {
22581
22887
  const project = resolveProject(app.db, request.params.name);
22582
22888
  const { runId } = request.params;
22583
- const run = app.db.select().from(runs).where(and17(eq24(runs.id, runId), eq24(runs.projectId, project.id))).get();
22889
+ const run = app.db.select().from(runs).where(and18(eq25(runs.id, runId), eq25(runs.projectId, project.id))).get();
22584
22890
  if (!run) {
22585
22891
  const err = notFound("Run", runId);
22586
22892
  return reply.code(err.statusCode).send(err.toJSON());
@@ -22593,8 +22899,8 @@ async function cdpRoutes(app, opts) {
22593
22899
  citedDomains: querySnapshots.citedDomains,
22594
22900
  screenshotPath: querySnapshots.screenshotPath,
22595
22901
  rawResponse: querySnapshots.rawResponse
22596
- }).from(querySnapshots).where(eq24(querySnapshots.runId, runId)).all());
22597
- const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq24(queries.projectId, project.id)).all();
22902
+ }).from(querySnapshots).where(eq25(querySnapshots.runId, runId)).all());
22903
+ const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq25(queries.projectId, project.id)).all();
22598
22904
  const queryMap = new Map(queryRows.map((q) => [q.id, q.query]));
22599
22905
  const byQuery = /* @__PURE__ */ new Map();
22600
22906
  for (const snap of snapshots) {
@@ -22677,7 +22983,7 @@ async function cdpRoutes(app, opts) {
22677
22983
 
22678
22984
  // ../api-routes/src/ga.ts
22679
22985
  import crypto21 from "crypto";
22680
- import { eq as eq25, desc as desc13, and as and18, sql as sql9 } from "drizzle-orm";
22986
+ import { eq as eq26, desc as desc13, and as and19, sql as sql10 } from "drizzle-orm";
22681
22987
  function gaLog(level, action, ctx) {
22682
22988
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
22683
22989
  const stream = level === "error" ? process.stderr : process.stdout;
@@ -22884,10 +23190,10 @@ async function ga4Routes(app, opts) {
22884
23190
  if (!saConn && !oauthConn) {
22885
23191
  throw notFound("GA4 connection", project.name);
22886
23192
  }
22887
- app.db.delete(gaTrafficSnapshots).where(eq25(gaTrafficSnapshots.projectId, project.id)).run();
22888
- app.db.delete(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).run();
22889
- app.db.delete(gaAiReferrals).where(eq25(gaAiReferrals.projectId, project.id)).run();
22890
- app.db.delete(gaSocialReferrals).where(eq25(gaSocialReferrals.projectId, project.id)).run();
23193
+ app.db.delete(gaTrafficSnapshots).where(eq26(gaTrafficSnapshots.projectId, project.id)).run();
23194
+ app.db.delete(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).run();
23195
+ app.db.delete(gaAiReferrals).where(eq26(gaAiReferrals.projectId, project.id)).run();
23196
+ app.db.delete(gaSocialReferrals).where(eq26(gaSocialReferrals.projectId, project.id)).run();
22891
23197
  const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
22892
23198
  opts.ga4CredentialStore?.deleteConnection(project.name);
22893
23199
  opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
@@ -22908,7 +23214,7 @@ async function ga4Routes(app, opts) {
22908
23214
  if (!connected) {
22909
23215
  return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
22910
23216
  }
22911
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
23217
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
22912
23218
  return {
22913
23219
  connected: true,
22914
23220
  propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
@@ -22972,10 +23278,10 @@ async function ga4Routes(app, opts) {
22972
23278
  app.db.transaction((tx) => {
22973
23279
  if (syncTraffic) {
22974
23280
  tx.delete(gaTrafficSnapshots).where(
22975
- and18(
22976
- eq25(gaTrafficSnapshots.projectId, project.id),
22977
- sql9`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
22978
- sql9`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
23281
+ and19(
23282
+ eq26(gaTrafficSnapshots.projectId, project.id),
23283
+ sql10`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
23284
+ sql10`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
22979
23285
  )
22980
23286
  ).run();
22981
23287
  for (const row of rows) {
@@ -22996,10 +23302,10 @@ async function ga4Routes(app, opts) {
22996
23302
  }
22997
23303
  if (syncAi) {
22998
23304
  tx.delete(gaAiReferrals).where(
22999
- and18(
23000
- eq25(gaAiReferrals.projectId, project.id),
23001
- sql9`${gaAiReferrals.date} >= ${summary.periodStart}`,
23002
- sql9`${gaAiReferrals.date} <= ${summary.periodEnd}`
23305
+ and19(
23306
+ eq26(gaAiReferrals.projectId, project.id),
23307
+ sql10`${gaAiReferrals.date} >= ${summary.periodStart}`,
23308
+ sql10`${gaAiReferrals.date} <= ${summary.periodEnd}`
23003
23309
  )
23004
23310
  ).run();
23005
23311
  for (const row of aiReferrals) {
@@ -23022,10 +23328,10 @@ async function ga4Routes(app, opts) {
23022
23328
  }
23023
23329
  if (syncSocial) {
23024
23330
  tx.delete(gaSocialReferrals).where(
23025
- and18(
23026
- eq25(gaSocialReferrals.projectId, project.id),
23027
- sql9`${gaSocialReferrals.date} >= ${summary.periodStart}`,
23028
- sql9`${gaSocialReferrals.date} <= ${summary.periodEnd}`
23331
+ and19(
23332
+ eq26(gaSocialReferrals.projectId, project.id),
23333
+ sql10`${gaSocialReferrals.date} >= ${summary.periodStart}`,
23334
+ sql10`${gaSocialReferrals.date} <= ${summary.periodEnd}`
23029
23335
  )
23030
23336
  ).run();
23031
23337
  for (const row of socialReferrals) {
@@ -23044,7 +23350,7 @@ async function ga4Routes(app, opts) {
23044
23350
  }
23045
23351
  }
23046
23352
  if (syncSummary) {
23047
- tx.delete(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).run();
23353
+ tx.delete(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).run();
23048
23354
  tx.insert(gaTrafficSummaries).values({
23049
23355
  id: crypto21.randomUUID(),
23050
23356
  projectId: project.id,
@@ -23056,7 +23362,7 @@ async function ga4Routes(app, opts) {
23056
23362
  syncedAt: now,
23057
23363
  syncRunId: runId
23058
23364
  }).run();
23059
- tx.delete(gaTrafficWindowSummaries).where(eq25(gaTrafficWindowSummaries.projectId, project.id)).run();
23365
+ tx.delete(gaTrafficWindowSummaries).where(eq26(gaTrafficWindowSummaries.projectId, project.id)).run();
23060
23366
  for (const ws of windowSummaries) {
23061
23367
  tx.insert(gaTrafficWindowSummaries).values({
23062
23368
  id: crypto21.randomUUID(),
@@ -23074,7 +23380,7 @@ async function ga4Routes(app, opts) {
23074
23380
  }
23075
23381
  }
23076
23382
  });
23077
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq25(runs.id, runId)).run();
23383
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq26(runs.id, runId)).run();
23078
23384
  const syncedComponents = only ? [
23079
23385
  ...syncTraffic ? ["traffic"] : [],
23080
23386
  ...syncSummary ? ["summary"] : [],
@@ -23103,7 +23409,7 @@ async function ga4Routes(app, opts) {
23103
23409
  } catch (e) {
23104
23410
  const msg = e instanceof Error ? e.message : String(e);
23105
23411
  gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
23106
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq25(runs.id, runId)).run();
23412
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq26(runs.id, runId)).run();
23107
23413
  throw e;
23108
23414
  }
23109
23415
  });
@@ -23114,66 +23420,66 @@ async function ga4Routes(app, opts) {
23114
23420
  const window = parseWindow(request.query.window);
23115
23421
  const cutoff = windowCutoff(window);
23116
23422
  const cutoffDate = cutoff?.slice(0, 10) ?? null;
23117
- const snapshotConditions = [eq25(gaTrafficSnapshots.projectId, project.id)];
23118
- if (cutoffDate) snapshotConditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
23119
- const aiConditions = [eq25(gaAiReferrals.projectId, project.id)];
23120
- if (cutoffDate) aiConditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
23121
- const socialConditions = [eq25(gaSocialReferrals.projectId, project.id)];
23122
- if (cutoffDate) socialConditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
23423
+ const snapshotConditions = [eq26(gaTrafficSnapshots.projectId, project.id)];
23424
+ if (cutoffDate) snapshotConditions.push(sql10`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
23425
+ const aiConditions = [eq26(gaAiReferrals.projectId, project.id)];
23426
+ if (cutoffDate) aiConditions.push(sql10`${gaAiReferrals.date} >= ${cutoffDate}`);
23427
+ const socialConditions = [eq26(gaSocialReferrals.projectId, project.id)];
23428
+ if (cutoffDate) socialConditions.push(sql10`${gaSocialReferrals.date} >= ${cutoffDate}`);
23123
23429
  const windowSummaryRow = cutoffDate ? app.db.select({
23124
23430
  totalSessions: gaTrafficWindowSummaries.totalSessions,
23125
23431
  totalOrganicSessions: gaTrafficWindowSummaries.totalOrganicSessions,
23126
23432
  totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
23127
23433
  totalUsers: gaTrafficWindowSummaries.totalUsers
23128
23434
  }).from(gaTrafficWindowSummaries).where(
23129
- and18(
23130
- eq25(gaTrafficWindowSummaries.projectId, project.id),
23131
- eq25(gaTrafficWindowSummaries.windowKey, window)
23435
+ and19(
23436
+ eq26(gaTrafficWindowSummaries.projectId, project.id),
23437
+ eq26(gaTrafficWindowSummaries.windowKey, window)
23132
23438
  )
23133
23439
  ).get() : null;
23134
23440
  const snapshotTotalsRow = cutoffDate && !windowSummaryRow ? app.db.select({
23135
- totalSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
23136
- totalOrganicSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
23137
- totalUsers: sql9`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
23138
- }).from(gaTrafficSnapshots).where(and18(...snapshotConditions)).get() : null;
23441
+ totalSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
23442
+ totalOrganicSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
23443
+ totalUsers: sql10`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
23444
+ }).from(gaTrafficSnapshots).where(and19(...snapshotConditions)).get() : null;
23139
23445
  const summaryRow = cutoffDate ? windowSummaryRow ?? snapshotTotalsRow : app.db.select({
23140
23446
  totalSessions: gaTrafficSummaries.totalSessions,
23141
23447
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
23142
23448
  totalUsers: gaTrafficSummaries.totalUsers
23143
- }).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).get();
23449
+ }).from(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).get();
23144
23450
  const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
23145
- totalDirectSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
23146
- }).from(gaTrafficSnapshots).where(and18(...snapshotConditions)).get();
23451
+ totalDirectSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
23452
+ }).from(gaTrafficSnapshots).where(and19(...snapshotConditions)).get();
23147
23453
  const summaryMeta = app.db.select({
23148
23454
  periodStart: gaTrafficSummaries.periodStart,
23149
23455
  periodEnd: gaTrafficSummaries.periodEnd
23150
- }).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).get();
23456
+ }).from(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).get();
23151
23457
  const rows = app.db.select({
23152
- landingPage: sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
23153
- sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
23154
- organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
23155
- directSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
23156
- users: sql9`SUM(${gaTrafficSnapshots.users})`
23157
- }).from(gaTrafficSnapshots).where(and18(...snapshotConditions)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
23458
+ landingPage: sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
23459
+ sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
23460
+ organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
23461
+ directSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
23462
+ users: sql10`SUM(${gaTrafficSnapshots.users})`
23463
+ }).from(gaTrafficSnapshots).where(and19(...snapshotConditions)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
23158
23464
  const aiReferralRows = app.db.select({
23159
23465
  source: gaAiReferrals.source,
23160
23466
  medium: gaAiReferrals.medium,
23161
23467
  sourceDimension: gaAiReferrals.sourceDimension,
23162
- sessions: sql9`SUM(${gaAiReferrals.sessions})`,
23163
- users: sql9`SUM(${gaAiReferrals.users})`
23164
- }).from(gaAiReferrals).where(and18(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
23468
+ sessions: sql10`SUM(${gaAiReferrals.sessions})`,
23469
+ users: sql10`SUM(${gaAiReferrals.users})`
23470
+ }).from(gaAiReferrals).where(and19(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
23165
23471
  const aiReferralLandingPageRows = app.db.select({
23166
23472
  source: gaAiReferrals.source,
23167
23473
  medium: gaAiReferrals.medium,
23168
23474
  sourceDimension: gaAiReferrals.sourceDimension,
23169
- landingPage: sql9`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
23170
- sessions: sql9`SUM(${gaAiReferrals.sessions})`,
23171
- users: sql9`SUM(${gaAiReferrals.users})`
23172
- }).from(gaAiReferrals).where(and18(...aiConditions)).groupBy(
23475
+ landingPage: sql10`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
23476
+ sessions: sql10`SUM(${gaAiReferrals.sessions})`,
23477
+ users: sql10`SUM(${gaAiReferrals.users})`
23478
+ }).from(gaAiReferrals).where(and19(...aiConditions)).groupBy(
23173
23479
  gaAiReferrals.source,
23174
23480
  gaAiReferrals.medium,
23175
23481
  gaAiReferrals.sourceDimension,
23176
- sql9`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
23482
+ sql10`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
23177
23483
  ).all();
23178
23484
  const aiReferrals = pickWinningDimension(
23179
23485
  aiReferralRows,
@@ -23184,10 +23490,10 @@ async function ga4Routes(app, opts) {
23184
23490
  (r) => `${r.source}\0${r.medium}\0${r.landingPage}`
23185
23491
  );
23186
23492
  const aiDeduped = app.db.select({
23187
- sessions: sql9`COALESCE(SUM(max_sessions), 0)`,
23188
- users: sql9`COALESCE(SUM(max_users), 0)`
23493
+ sessions: sql10`COALESCE(SUM(max_sessions), 0)`,
23494
+ users: sql10`COALESCE(SUM(max_users), 0)`
23189
23495
  }).from(
23190
- sql9`(
23496
+ sql10`(
23191
23497
  SELECT date, source, medium,
23192
23498
  MAX(dimension_sessions) AS max_sessions,
23193
23499
  MAX(dimension_users) AS max_users
@@ -23196,7 +23502,7 @@ async function ga4Routes(app, opts) {
23196
23502
  SUM(sessions) AS dimension_sessions,
23197
23503
  SUM(users) AS dimension_users
23198
23504
  FROM ga_ai_referrals
23199
- WHERE project_id = ${project.id}${cutoffDate ? sql9` AND date >= ${cutoffDate}` : sql9``}
23505
+ WHERE project_id = ${project.id}${cutoffDate ? sql10` AND date >= ${cutoffDate}` : sql10``}
23200
23506
  GROUP BY date, source, medium, source_dimension
23201
23507
  )
23202
23508
  GROUP BY date, source, medium
@@ -23204,9 +23510,9 @@ async function ga4Routes(app, opts) {
23204
23510
  ).get();
23205
23511
  const aiBySessionRows = app.db.select({
23206
23512
  channelGroup: gaAiReferrals.channelGroup,
23207
- sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
23208
- users: sql9`COALESCE(SUM(${gaAiReferrals.users}), 0)`
23209
- }).from(gaAiReferrals).where(and18(...aiConditions, eq25(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
23513
+ sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
23514
+ users: sql10`COALESCE(SUM(${gaAiReferrals.users}), 0)`
23515
+ }).from(gaAiReferrals).where(and19(...aiConditions, eq26(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
23210
23516
  const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
23211
23517
  let aiBySessionUsers = 0;
23212
23518
  for (const row of aiBySessionRows) {
@@ -23218,14 +23524,14 @@ async function ga4Routes(app, opts) {
23218
23524
  source: gaSocialReferrals.source,
23219
23525
  medium: gaSocialReferrals.medium,
23220
23526
  channelGroup: gaSocialReferrals.channelGroup,
23221
- sessions: sql9`SUM(${gaSocialReferrals.sessions})`,
23222
- users: sql9`SUM(${gaSocialReferrals.users})`
23223
- }).from(gaSocialReferrals).where(and18(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql9`SUM(${gaSocialReferrals.sessions}) DESC`).all();
23527
+ sessions: sql10`SUM(${gaSocialReferrals.sessions})`,
23528
+ users: sql10`SUM(${gaSocialReferrals.users})`
23529
+ }).from(gaSocialReferrals).where(and19(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql10`SUM(${gaSocialReferrals.sessions}) DESC`).all();
23224
23530
  const socialTotals = app.db.select({
23225
- sessions: sql9`SUM(${gaSocialReferrals.sessions})`,
23226
- users: sql9`SUM(${gaSocialReferrals.users})`
23227
- }).from(gaSocialReferrals).where(and18(...socialConditions)).get();
23228
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq25(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
23531
+ sessions: sql10`SUM(${gaSocialReferrals.sessions})`,
23532
+ users: sql10`SUM(${gaSocialReferrals.users})`
23533
+ }).from(gaSocialReferrals).where(and19(...socialConditions)).get();
23534
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
23229
23535
  const total = summaryRow?.totalSessions ?? 0;
23230
23536
  const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
23231
23537
  const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
@@ -23305,22 +23611,22 @@ async function ga4Routes(app, opts) {
23305
23611
  const project = resolveProject(app.db, request.params.name);
23306
23612
  requireGa4Connection(opts, project.name, project.canonicalDomain);
23307
23613
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
23308
- const conditions = [eq25(gaAiReferrals.projectId, project.id)];
23309
- if (cutoffDate) conditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
23614
+ const conditions = [eq26(gaAiReferrals.projectId, project.id)];
23615
+ if (cutoffDate) conditions.push(sql10`${gaAiReferrals.date} >= ${cutoffDate}`);
23310
23616
  const rows = app.db.select({
23311
23617
  date: gaAiReferrals.date,
23312
23618
  source: gaAiReferrals.source,
23313
23619
  medium: gaAiReferrals.medium,
23314
- landingPage: sql9`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
23620
+ landingPage: sql10`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
23315
23621
  sourceDimension: gaAiReferrals.sourceDimension,
23316
- sessions: sql9`SUM(${gaAiReferrals.sessions})`,
23317
- users: sql9`SUM(${gaAiReferrals.users})`
23318
- }).from(gaAiReferrals).where(and18(...conditions)).groupBy(
23622
+ sessions: sql10`SUM(${gaAiReferrals.sessions})`,
23623
+ users: sql10`SUM(${gaAiReferrals.users})`
23624
+ }).from(gaAiReferrals).where(and19(...conditions)).groupBy(
23319
23625
  gaAiReferrals.date,
23320
23626
  gaAiReferrals.source,
23321
23627
  gaAiReferrals.medium,
23322
23628
  gaAiReferrals.sourceDimension,
23323
- sql9`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
23629
+ sql10`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`
23324
23630
  ).orderBy(gaAiReferrals.date).all();
23325
23631
  return rows;
23326
23632
  });
@@ -23328,8 +23634,8 @@ async function ga4Routes(app, opts) {
23328
23634
  const project = resolveProject(app.db, request.params.name);
23329
23635
  requireGa4Connection(opts, project.name, project.canonicalDomain);
23330
23636
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
23331
- const conditions = [eq25(gaSocialReferrals.projectId, project.id)];
23332
- if (cutoffDate) conditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
23637
+ const conditions = [eq26(gaSocialReferrals.projectId, project.id)];
23638
+ if (cutoffDate) conditions.push(sql10`${gaSocialReferrals.date} >= ${cutoffDate}`);
23333
23639
  const rows = app.db.select({
23334
23640
  date: gaSocialReferrals.date,
23335
23641
  source: gaSocialReferrals.source,
@@ -23337,7 +23643,7 @@ async function ga4Routes(app, opts) {
23337
23643
  channelGroup: gaSocialReferrals.channelGroup,
23338
23644
  sessions: gaSocialReferrals.sessions,
23339
23645
  users: gaSocialReferrals.users
23340
- }).from(gaSocialReferrals).where(and18(...conditions)).orderBy(gaSocialReferrals.date).all();
23646
+ }).from(gaSocialReferrals).where(and19(...conditions)).orderBy(gaSocialReferrals.date).all();
23341
23647
  return rows;
23342
23648
  });
23343
23649
  app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
@@ -23350,10 +23656,10 @@ async function ga4Routes(app, opts) {
23350
23656
  d.setDate(d.getDate() - n);
23351
23657
  return fmt(d);
23352
23658
  };
23353
- const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and18(
23354
- eq25(gaSocialReferrals.projectId, project.id),
23355
- sql9`${gaSocialReferrals.date} >= ${from}`,
23356
- sql9`${gaSocialReferrals.date} < ${to}`
23659
+ const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and19(
23660
+ eq26(gaSocialReferrals.projectId, project.id),
23661
+ sql10`${gaSocialReferrals.date} >= ${from}`,
23662
+ sql10`${gaSocialReferrals.date} < ${to}`
23357
23663
  )).get();
23358
23664
  const current7d = sumSocial(daysAgo(7), fmt(today));
23359
23665
  const prev7d = sumSocial(daysAgo(14), daysAgo(7));
@@ -23362,19 +23668,19 @@ async function ga4Routes(app, opts) {
23362
23668
  const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
23363
23669
  const sourceCurrent = app.db.select({
23364
23670
  source: gaSocialReferrals.source,
23365
- sessions: sql9`SUM(${gaSocialReferrals.sessions})`
23366
- }).from(gaSocialReferrals).where(and18(
23367
- eq25(gaSocialReferrals.projectId, project.id),
23368
- sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
23369
- sql9`${gaSocialReferrals.date} < ${fmt(today)}`
23671
+ sessions: sql10`SUM(${gaSocialReferrals.sessions})`
23672
+ }).from(gaSocialReferrals).where(and19(
23673
+ eq26(gaSocialReferrals.projectId, project.id),
23674
+ sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
23675
+ sql10`${gaSocialReferrals.date} < ${fmt(today)}`
23370
23676
  )).groupBy(gaSocialReferrals.source).all();
23371
23677
  const sourcePrev = app.db.select({
23372
23678
  source: gaSocialReferrals.source,
23373
- sessions: sql9`SUM(${gaSocialReferrals.sessions})`
23374
- }).from(gaSocialReferrals).where(and18(
23375
- eq25(gaSocialReferrals.projectId, project.id),
23376
- sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
23377
- sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`
23679
+ sessions: sql10`SUM(${gaSocialReferrals.sessions})`
23680
+ }).from(gaSocialReferrals).where(and19(
23681
+ eq26(gaSocialReferrals.projectId, project.id),
23682
+ sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
23683
+ sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`
23378
23684
  )).groupBy(gaSocialReferrals.source).all();
23379
23685
  const prevMap = new Map(sourcePrev.map((r) => [r.source, r.sessions]));
23380
23686
  let biggestMover = null;
@@ -23413,16 +23719,16 @@ async function ga4Routes(app, opts) {
23413
23719
  return fmt(d);
23414
23720
  };
23415
23721
  const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
23416
- const sumTotal = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and18(eq25(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
23417
- const sumOrganic = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and18(eq25(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
23418
- const sumDirect = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and18(eq25(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
23419
- const sumAi = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and18(
23420
- eq25(gaAiReferrals.projectId, project.id),
23421
- sql9`${gaAiReferrals.date} >= ${from}`,
23422
- sql9`${gaAiReferrals.date} < ${to}`,
23423
- eq25(gaAiReferrals.sourceDimension, "session")
23722
+ const sumTotal = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and19(eq26(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
23723
+ const sumOrganic = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and19(eq26(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
23724
+ const sumDirect = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and19(eq26(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
23725
+ const sumAi = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and19(
23726
+ eq26(gaAiReferrals.projectId, project.id),
23727
+ sql10`${gaAiReferrals.date} >= ${from}`,
23728
+ sql10`${gaAiReferrals.date} < ${to}`,
23729
+ eq26(gaAiReferrals.sourceDimension, "session")
23424
23730
  )).get();
23425
- const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and18(eq25(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${from}`, sql9`${gaSocialReferrals.date} < ${to}`)).get();
23731
+ const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and19(eq26(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${from}`, sql10`${gaSocialReferrals.date} < ${to}`)).get();
23426
23732
  const todayStr = fmt(today);
23427
23733
  const buildTrend = (sum) => {
23428
23734
  const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
@@ -23431,17 +23737,17 @@ async function ga4Routes(app, opts) {
23431
23737
  const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
23432
23738
  return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
23433
23739
  };
23434
- const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and18(
23435
- eq25(gaAiReferrals.projectId, project.id),
23436
- sql9`${gaAiReferrals.date} >= ${daysAgo(7)}`,
23437
- sql9`${gaAiReferrals.date} < ${todayStr}`,
23438
- eq25(gaAiReferrals.sourceDimension, "session")
23740
+ const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and19(
23741
+ eq26(gaAiReferrals.projectId, project.id),
23742
+ sql10`${gaAiReferrals.date} >= ${daysAgo(7)}`,
23743
+ sql10`${gaAiReferrals.date} < ${todayStr}`,
23744
+ eq26(gaAiReferrals.sourceDimension, "session")
23439
23745
  )).groupBy(gaAiReferrals.source).all();
23440
- const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and18(
23441
- eq25(gaAiReferrals.projectId, project.id),
23442
- sql9`${gaAiReferrals.date} >= ${daysAgo(14)}`,
23443
- sql9`${gaAiReferrals.date} < ${daysAgo(7)}`,
23444
- eq25(gaAiReferrals.sourceDimension, "session")
23746
+ const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and19(
23747
+ eq26(gaAiReferrals.projectId, project.id),
23748
+ sql10`${gaAiReferrals.date} >= ${daysAgo(14)}`,
23749
+ sql10`${gaAiReferrals.date} < ${daysAgo(7)}`,
23750
+ eq26(gaAiReferrals.sourceDimension, "session")
23445
23751
  )).groupBy(gaAiReferrals.source).all();
23446
23752
  const findBiggestMover = (current, prev) => {
23447
23753
  const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
@@ -23457,8 +23763,8 @@ async function ga4Routes(app, opts) {
23457
23763
  }
23458
23764
  return mover;
23459
23765
  };
23460
- const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and18(eq25(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql9`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
23461
- const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and18(eq25(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
23766
+ const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and19(eq26(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql10`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
23767
+ const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and19(eq26(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
23462
23768
  return {
23463
23769
  total: buildTrend(sumTotal),
23464
23770
  organic: buildTrend(sumOrganic),
@@ -23473,14 +23779,14 @@ async function ga4Routes(app, opts) {
23473
23779
  const project = resolveProject(app.db, request.params.name);
23474
23780
  requireGa4Connection(opts, project.name, project.canonicalDomain);
23475
23781
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
23476
- const conditions = [eq25(gaTrafficSnapshots.projectId, project.id)];
23477
- if (cutoffDate) conditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
23782
+ const conditions = [eq26(gaTrafficSnapshots.projectId, project.id)];
23783
+ if (cutoffDate) conditions.push(sql10`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
23478
23784
  const rows = app.db.select({
23479
23785
  date: gaTrafficSnapshots.date,
23480
- sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
23481
- organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
23482
- users: sql9`SUM(${gaTrafficSnapshots.users})`
23483
- }).from(gaTrafficSnapshots).where(and18(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
23786
+ sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
23787
+ organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
23788
+ users: sql10`SUM(${gaTrafficSnapshots.users})`
23789
+ }).from(gaTrafficSnapshots).where(and19(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
23484
23790
  return rows.map((r) => ({
23485
23791
  date: r.date,
23486
23792
  sessions: r.sessions ?? 0,
@@ -23492,11 +23798,11 @@ async function ga4Routes(app, opts) {
23492
23798
  const project = resolveProject(app.db, request.params.name);
23493
23799
  requireGa4Connection(opts, project.name, project.canonicalDomain);
23494
23800
  const trafficPages = app.db.select({
23495
- landingPage: sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
23496
- sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
23497
- organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
23498
- users: sql9`SUM(${gaTrafficSnapshots.users})`
23499
- }).from(gaTrafficSnapshots).where(eq25(gaTrafficSnapshots.projectId, project.id)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
23801
+ landingPage: sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
23802
+ sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
23803
+ organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
23804
+ users: sql10`SUM(${gaTrafficSnapshots.users})`
23805
+ }).from(gaTrafficSnapshots).where(eq26(gaTrafficSnapshots.projectId, project.id)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
23500
23806
  return {
23501
23807
  pages: trafficPages.map((r) => ({
23502
23808
  landingPage: r.landingPage,
@@ -25144,7 +25450,7 @@ async function wordpressRoutes(app, opts) {
25144
25450
 
25145
25451
  // ../api-routes/src/backlinks.ts
25146
25452
  import crypto23 from "crypto";
25147
- import { and as and20, asc as asc3, desc as desc14, eq as eq26, sql as sql10 } from "drizzle-orm";
25453
+ import { and as and21, asc as asc4, desc as desc14, eq as eq27, sql as sql11 } from "drizzle-orm";
25148
25454
 
25149
25455
  // ../integration-commoncrawl/src/constants.ts
25150
25456
  import os2 from "os";
@@ -25434,7 +25740,7 @@ async function queryBacklinks(opts) {
25434
25740
  const reversed = opts.targets.map(reverseDomain);
25435
25741
  const targetList = reversed.map(quote).join(", ");
25436
25742
  const limitClause = opts.limitPerTarget ? `QUALIFY row_number() OVER (PARTITION BY t.target_rev_domain ORDER BY v.num_hosts DESC) <= ${Math.floor(opts.limitPerTarget)}` : "";
25437
- const sql13 = `
25743
+ const sql14 = `
25438
25744
  WITH vertices AS (
25439
25745
  SELECT * FROM read_csv(
25440
25746
  ${quote(opts.vertexPath)},
@@ -25470,7 +25776,7 @@ async function queryBacklinks(opts) {
25470
25776
  const conn = await instance.connect();
25471
25777
  let rows;
25472
25778
  try {
25473
- const reader = await conn.runAndReadAll(sql13);
25779
+ const reader = await conn.runAndReadAll(sql14);
25474
25780
  rows = reader.getRowObjects();
25475
25781
  } finally {
25476
25782
  conn.disconnectSync?.();
@@ -25547,7 +25853,7 @@ function pruneCachedRelease(release, opts = {}) {
25547
25853
  }
25548
25854
 
25549
25855
  // ../api-routes/src/backlinks-filter.ts
25550
- import { and as and19, ne as ne3, notLike } from "drizzle-orm";
25856
+ import { and as and20, ne as ne3, notLike } from "drizzle-orm";
25551
25857
  var BACKLINK_FILTER_PATTERNS = [
25552
25858
  "*.google.com",
25553
25859
  "*.googleusercontent.com",
@@ -25570,7 +25876,7 @@ function backlinkCrawlerExclusionClause() {
25570
25876
  conditions.push(ne3(backlinkDomains.linkingDomain, pattern));
25571
25877
  }
25572
25878
  }
25573
- const combined = and19(...conditions);
25879
+ const combined = and20(...conditions);
25574
25880
  if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
25575
25881
  return combined;
25576
25882
  }
@@ -25641,11 +25947,11 @@ function mapRunRow(row) {
25641
25947
  }
25642
25948
  function latestSummaryForProject(db, projectId, source, release) {
25643
25949
  const conditions = [
25644
- eq26(backlinkSummaries.projectId, projectId),
25645
- eq26(backlinkSummaries.source, source)
25950
+ eq27(backlinkSummaries.projectId, projectId),
25951
+ eq27(backlinkSummaries.source, source)
25646
25952
  ];
25647
- if (release) conditions.push(eq26(backlinkSummaries.release, release));
25648
- return db.select().from(backlinkSummaries).where(and20(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
25953
+ if (release) conditions.push(eq27(backlinkSummaries.release, release));
25954
+ return db.select().from(backlinkSummaries).where(and21(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
25649
25955
  }
25650
25956
  function parseExcludeCrawlers(value) {
25651
25957
  if (!value) return false;
@@ -25653,19 +25959,19 @@ function parseExcludeCrawlers(value) {
25653
25959
  return lower === "1" || lower === "true" || lower === "yes";
25654
25960
  }
25655
25961
  function computeFilteredSummary(db, base) {
25656
- const baseDomainCondition = and20(
25657
- eq26(backlinkDomains.projectId, base.projectId),
25658
- eq26(backlinkDomains.source, base.source),
25659
- eq26(backlinkDomains.release, base.release)
25962
+ const baseDomainCondition = and21(
25963
+ eq27(backlinkDomains.projectId, base.projectId),
25964
+ eq27(backlinkDomains.source, base.source),
25965
+ eq27(backlinkDomains.release, base.release)
25660
25966
  );
25661
- const filteredCondition = and20(baseDomainCondition, backlinkCrawlerExclusionClause());
25967
+ const filteredCondition = and21(baseDomainCondition, backlinkCrawlerExclusionClause());
25662
25968
  const unfilteredAgg = db.select({
25663
- count: sql10`count(*)`,
25664
- total: sql10`coalesce(sum(${backlinkDomains.numHosts}), 0)`
25969
+ count: sql11`count(*)`,
25970
+ total: sql11`coalesce(sum(${backlinkDomains.numHosts}), 0)`
25665
25971
  }).from(backlinkDomains).where(baseDomainCondition).get();
25666
25972
  const filteredAgg = db.select({
25667
- count: sql10`count(*)`,
25668
- total: sql10`coalesce(sum(${backlinkDomains.numHosts}), 0)`
25973
+ count: sql11`count(*)`,
25974
+ total: sql11`coalesce(sum(${backlinkDomains.numHosts}), 0)`
25669
25975
  }).from(backlinkDomains).where(filteredCondition).get();
25670
25976
  const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(desc14(backlinkDomains.numHosts)).limit(10).all();
25671
25977
  const totalLinkingDomains = Number(filteredAgg?.count ?? 0);
@@ -25688,13 +25994,13 @@ function computeFilteredSummary(db, base) {
25688
25994
  };
25689
25995
  }
25690
25996
  function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
25691
- const summary = db.select().from(backlinkSummaries).where(and20(eq26(backlinkSummaries.projectId, projectId), eq26(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
25997
+ const summary = db.select().from(backlinkSummaries).where(and21(eq27(backlinkSummaries.projectId, projectId), eq27(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
25692
25998
  let totalLinkingDomains = summary?.totalLinkingDomains ?? 0;
25693
25999
  if (summary && excludeCrawlers) {
25694
- const filtered = db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(and20(
25695
- eq26(backlinkDomains.projectId, projectId),
25696
- eq26(backlinkDomains.source, source),
25697
- eq26(backlinkDomains.release, summary.release),
26000
+ const filtered = db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(and21(
26001
+ eq27(backlinkDomains.projectId, projectId),
26002
+ eq27(backlinkDomains.source, source),
26003
+ eq27(backlinkDomains.release, summary.release),
25698
26004
  backlinkCrawlerExclusionClause()
25699
26005
  )).get();
25700
26006
  totalLinkingDomains = Number(filtered?.count ?? 0);
@@ -25709,7 +26015,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
25709
26015
  };
25710
26016
  }
25711
26017
  function computeSourceAvailability(db, project, bingStore, excludeCrawlers) {
25712
- const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
26018
+ const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq27(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
25713
26019
  const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
25714
26020
  const bingConnected = !!bingStore?.getConnection(project.canonicalDomain);
25715
26021
  const sources = [
@@ -25765,7 +26071,7 @@ async function backlinksRoutes(app, opts) {
25765
26071
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
25766
26072
  );
25767
26073
  }
25768
- const existing = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.release, release)).get();
26074
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq27(ccReleaseSyncs.release, release)).get();
25769
26075
  const now = (/* @__PURE__ */ new Date()).toISOString();
25770
26076
  if (existing) {
25771
26077
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -25776,9 +26082,9 @@ async function backlinksRoutes(app, opts) {
25776
26082
  phaseDetail: null,
25777
26083
  error: null,
25778
26084
  updatedAt: now
25779
- }).where(eq26(ccReleaseSyncs.id, existing.id)).run();
26085
+ }).where(eq27(ccReleaseSyncs.id, existing.id)).run();
25780
26086
  opts.onReleaseSyncRequested(existing.id, release);
25781
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.id, existing.id)).get();
26087
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq27(ccReleaseSyncs.id, existing.id)).get();
25782
26088
  return reply.status(200).send(mapSyncRow(refreshed));
25783
26089
  }
25784
26090
  const id = crypto23.randomUUID();
@@ -25790,7 +26096,7 @@ async function backlinksRoutes(app, opts) {
25790
26096
  updatedAt: now
25791
26097
  }).run();
25792
26098
  opts.onReleaseSyncRequested(id, release);
25793
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq26(ccReleaseSyncs.id, id)).get();
26099
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq27(ccReleaseSyncs.id, id)).get();
25794
26100
  return reply.status(201).send(mapSyncRow(inserted));
25795
26101
  });
25796
26102
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
@@ -25848,7 +26154,7 @@ async function backlinksRoutes(app, opts) {
25848
26154
  createdAt: now
25849
26155
  }).run();
25850
26156
  opts.onBacklinkExtractRequested(runId, project.id, release);
25851
- const run = app.db.select().from(runs).where(eq26(runs.id, runId)).get();
26157
+ const run = app.db.select().from(runs).where(eq27(runs.id, runId)).get();
25852
26158
  return reply.status(201).send(mapRunRow(run));
25853
26159
  });
25854
26160
  app.get(
@@ -25874,13 +26180,13 @@ async function backlinksRoutes(app, opts) {
25874
26180
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
25875
26181
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
25876
26182
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
25877
- const baseDomainCondition = and20(
25878
- eq26(backlinkDomains.projectId, project.id),
25879
- eq26(backlinkDomains.source, source),
25880
- eq26(backlinkDomains.release, targetRelease)
26183
+ const baseDomainCondition = and21(
26184
+ eq27(backlinkDomains.projectId, project.id),
26185
+ eq27(backlinkDomains.source, source),
26186
+ eq27(backlinkDomains.release, targetRelease)
25881
26187
  );
25882
- const domainCondition = excludeCrawlers ? and20(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
25883
- const totalRow = app.db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(domainCondition).get();
26188
+ const domainCondition = excludeCrawlers ? and21(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
26189
+ const totalRow = app.db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(domainCondition).get();
25884
26190
  const rows = app.db.select({
25885
26191
  linkingDomain: backlinkDomains.linkingDomain,
25886
26192
  numHosts: backlinkDomains.numHosts,
@@ -25903,7 +26209,7 @@ async function backlinksRoutes(app, opts) {
25903
26209
  async (request, reply) => {
25904
26210
  const project = resolveProject(app.db, request.params.name);
25905
26211
  const source = parseSourceParam(request.query.source);
25906
- const rows = app.db.select().from(backlinkSummaries).where(and20(eq26(backlinkSummaries.projectId, project.id), eq26(backlinkSummaries.source, source))).orderBy(asc3(backlinkSummaries.queriedAt)).all();
26212
+ const rows = app.db.select().from(backlinkSummaries).where(and21(eq27(backlinkSummaries.projectId, project.id), eq27(backlinkSummaries.source, source))).orderBy(asc4(backlinkSummaries.queriedAt)).all();
25907
26213
  const response = rows.map((r) => ({
25908
26214
  release: r.release,
25909
26215
  totalLinkingDomains: r.totalLinkingDomains,
@@ -25950,7 +26256,7 @@ async function backlinksRoutes(app, opts) {
25950
26256
  createdAt: now
25951
26257
  }).run();
25952
26258
  opts.onBingBacklinkSyncRequested(runId, project.id);
25953
- const run = app.db.select().from(runs).where(eq26(runs.id, runId)).get();
26259
+ const run = app.db.select().from(runs).where(eq27(runs.id, runId)).get();
25954
26260
  return reply.status(201).send(mapRunRow(run));
25955
26261
  }
25956
26262
  );
@@ -25959,7 +26265,7 @@ async function backlinksRoutes(app, opts) {
25959
26265
  // ../api-routes/src/traffic.ts
25960
26266
  import crypto25 from "crypto";
25961
26267
  import { Agent as UndiciAgent } from "undici";
25962
- import { and as and21, desc as desc15, eq as eq27, gte as gte4, lte as lte3, sql as sql11 } from "drizzle-orm";
26268
+ import { and as and22, desc as desc15, eq as eq28, gte as gte4, lte as lte3, sql as sql12 } from "drizzle-orm";
25963
26269
 
25964
26270
  // ../integration-cloud-run/src/auth.ts
25965
26271
  import crypto24 from "crypto";
@@ -29161,14 +29467,18 @@ function trimOrNull(value) {
29161
29467
  function buildEventId2(event) {
29162
29468
  return `wordpress:${event.observed_at}:${event.id}`;
29163
29469
  }
29164
- function normalizeWordpressTrafficEvent(event) {
29470
+ function normalizeWordpressTrafficEvent(event, site) {
29165
29471
  if (!event.observed_at) return null;
29166
29472
  if (typeof event.id !== "number" || !Number.isFinite(event.id)) return null;
29167
- const path8 = event.path?.trim();
29473
+ if (!event.path) return null;
29474
+ const path8 = event.path.trim();
29168
29475
  if (!path8) return null;
29169
29476
  const queryString = trimOrNull(event.query_string);
29170
29477
  const host = trimOrNull(event.host);
29171
29478
  const requestUrl = host ? `https://${host}${path8}${queryString ? `?${queryString}` : ""}` : `${path8}${queryString ? `?${queryString}` : ""}`;
29479
+ const labels = {};
29480
+ if (host) labels.host = host;
29481
+ if (site?.anonymous_id) labels.anonymousId = site.anonymous_id;
29172
29482
  return {
29173
29483
  sourceType: TrafficSourceTypes.wordpress,
29174
29484
  evidenceKind: TrafficEvidenceKinds["raw-request"],
@@ -29189,7 +29499,7 @@ function normalizeWordpressTrafficEvent(event) {
29189
29499
  responseSizeBytes: null,
29190
29500
  providerResource: {
29191
29501
  type: "wordpress_site",
29192
- labels: host ? { host } : {}
29502
+ labels
29193
29503
  },
29194
29504
  providerLabels: {}
29195
29505
  };
@@ -29294,7 +29604,7 @@ async function listWordpressTrafficEvents(options) {
29294
29604
  const entries = body.events ?? [];
29295
29605
  rawEntryCount += entries.length;
29296
29606
  for (const entry of entries) {
29297
- const normalized = normalizeWordpressTrafficEvent(entry);
29607
+ const normalized = normalizeWordpressTrafficEvent(entry, body.site);
29298
29608
  if (normalized) {
29299
29609
  events.push(normalized);
29300
29610
  } else {
@@ -29776,8 +30086,8 @@ async function runBackfillTask(options) {
29776
30086
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
29777
30087
  try {
29778
30088
  app.db.transaction((tx) => {
29779
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq27(runs.id, runId)).run();
29780
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq27(trafficSources.id, sourceRow.id)).run();
30089
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq28(runs.id, runId)).run();
30090
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq28(trafficSources.id, sourceRow.id)).run();
29781
30091
  });
29782
30092
  } catch {
29783
30093
  }
@@ -29792,7 +30102,7 @@ async function runBackfillTask(options) {
29792
30102
  if (allEvents.length === 0) {
29793
30103
  const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
29794
30104
  try {
29795
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq27(runs.id, runId)).run();
30105
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq28(runs.id, runId)).run();
29796
30106
  } catch {
29797
30107
  }
29798
30108
  return;
@@ -29814,29 +30124,29 @@ async function runBackfillTask(options) {
29814
30124
  try {
29815
30125
  app.db.transaction((tx) => {
29816
30126
  tx.delete(crawlerEventsHourly).where(
29817
- and21(
29818
- eq27(crawlerEventsHourly.sourceId, sourceRow.id),
30127
+ and22(
30128
+ eq28(crawlerEventsHourly.sourceId, sourceRow.id),
29819
30129
  gte4(crawlerEventsHourly.tsHour, windowStartIso),
29820
30130
  lte3(crawlerEventsHourly.tsHour, windowEndIso)
29821
30131
  )
29822
30132
  ).run();
29823
30133
  tx.delete(aiUserFetchEventsHourly).where(
29824
- and21(
29825
- eq27(aiUserFetchEventsHourly.sourceId, sourceRow.id),
30134
+ and22(
30135
+ eq28(aiUserFetchEventsHourly.sourceId, sourceRow.id),
29826
30136
  gte4(aiUserFetchEventsHourly.tsHour, windowStartIso),
29827
30137
  lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
29828
30138
  )
29829
30139
  ).run();
29830
30140
  tx.delete(aiReferralEventsHourly).where(
29831
- and21(
29832
- eq27(aiReferralEventsHourly.sourceId, sourceRow.id),
30141
+ and22(
30142
+ eq28(aiReferralEventsHourly.sourceId, sourceRow.id),
29833
30143
  gte4(aiReferralEventsHourly.tsHour, windowStartIso),
29834
30144
  lte3(aiReferralEventsHourly.tsHour, windowEndIso)
29835
30145
  )
29836
30146
  ).run();
29837
30147
  tx.delete(rawEventSamples).where(
29838
- and21(
29839
- eq27(rawEventSamples.sourceId, sourceRow.id),
30148
+ and22(
30149
+ eq28(rawEventSamples.sourceId, sourceRow.id),
29840
30150
  gte4(rawEventSamples.ts, windowStartIso),
29841
30151
  lte3(rawEventSamples.ts, windowEndIso)
29842
30152
  )
@@ -29925,8 +30235,8 @@ async function runBackfillTask(options) {
29925
30235
  lastError: null,
29926
30236
  lastEventIds: newRingBuffer,
29927
30237
  updatedAt: finishedAt
29928
- }).where(eq27(trafficSources.id, sourceRow.id)).run();
29929
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq27(runs.id, runId)).run();
30238
+ }).where(eq28(trafficSources.id, sourceRow.id)).run();
30239
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq28(runs.id, runId)).run();
29930
30240
  });
29931
30241
  } catch (e) {
29932
30242
  markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -30008,7 +30318,7 @@ async function trafficRoutes(app, opts) {
30008
30318
  createdAt: existing?.createdAt ?? now,
30009
30319
  updatedAt: now
30010
30320
  });
30011
- const activeSource = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
30321
+ const activeSource = app.db.select().from(trafficSources).where(eq28(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
30012
30322
  const config = {
30013
30323
  gcpProjectId,
30014
30324
  serviceName: serviceName ?? null,
@@ -30024,8 +30334,8 @@ async function trafficRoutes(app, opts) {
30024
30334
  lastError: null,
30025
30335
  configJson: config,
30026
30336
  updatedAt: now
30027
- }).where(eq27(trafficSources.id, activeSource.id)).run();
30028
- sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
30337
+ }).where(eq28(trafficSources.id, activeSource.id)).run();
30338
+ sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, activeSource.id)).get();
30029
30339
  } else {
30030
30340
  const newId = crypto25.randomUUID();
30031
30341
  app.db.insert(trafficSources).values({
@@ -30042,7 +30352,7 @@ async function trafficRoutes(app, opts) {
30042
30352
  createdAt: now,
30043
30353
  updatedAt: now
30044
30354
  }).run();
30045
- sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, newId)).get();
30355
+ sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, newId)).get();
30046
30356
  }
30047
30357
  writeAuditLog(app.db, {
30048
30358
  projectId: project.id,
@@ -30094,7 +30404,7 @@ async function trafficRoutes(app, opts) {
30094
30404
  createdAt: existing?.createdAt ?? now,
30095
30405
  updatedAt: now
30096
30406
  });
30097
- const activeSource = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
30407
+ const activeSource = app.db.select().from(trafficSources).where(eq28(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
30098
30408
  const config = { baseUrl, username };
30099
30409
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
30100
30410
  let sourceRow;
@@ -30105,8 +30415,8 @@ async function trafficRoutes(app, opts) {
30105
30415
  lastError: null,
30106
30416
  configJson: config,
30107
30417
  updatedAt: now
30108
- }).where(eq27(trafficSources.id, activeSource.id)).run();
30109
- sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
30418
+ }).where(eq28(trafficSources.id, activeSource.id)).run();
30419
+ sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, activeSource.id)).get();
30110
30420
  } else {
30111
30421
  const newId = crypto25.randomUUID();
30112
30422
  app.db.insert(trafficSources).values({
@@ -30123,7 +30433,7 @@ async function trafficRoutes(app, opts) {
30123
30433
  createdAt: now,
30124
30434
  updatedAt: now
30125
30435
  }).run();
30126
- sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, newId)).get();
30436
+ sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, newId)).get();
30127
30437
  }
30128
30438
  writeAuditLog(app.db, {
30129
30439
  projectId: project.id,
@@ -30177,7 +30487,7 @@ async function trafficRoutes(app, opts) {
30177
30487
  createdAt: existing?.createdAt ?? now,
30178
30488
  updatedAt: now
30179
30489
  });
30180
- const activeSource = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
30490
+ const activeSource = app.db.select().from(trafficSources).where(eq28(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
30181
30491
  const config = { projectId, teamId, environment };
30182
30492
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
30183
30493
  const { sourceRow, scheduleCreated } = app.db.transaction((tx) => {
@@ -30189,8 +30499,8 @@ async function trafficRoutes(app, opts) {
30189
30499
  lastError: null,
30190
30500
  configJson: config,
30191
30501
  updatedAt: now
30192
- }).where(eq27(trafficSources.id, activeSource.id)).run();
30193
- row = tx.select().from(trafficSources).where(eq27(trafficSources.id, activeSource.id)).get();
30502
+ }).where(eq28(trafficSources.id, activeSource.id)).run();
30503
+ row = tx.select().from(trafficSources).where(eq28(trafficSources.id, activeSource.id)).get();
30194
30504
  } else {
30195
30505
  const newId = crypto25.randomUUID();
30196
30506
  tx.insert(trafficSources).values({
@@ -30215,12 +30525,12 @@ async function trafficRoutes(app, opts) {
30215
30525
  createdAt: now,
30216
30526
  updatedAt: now
30217
30527
  }).run();
30218
- row = tx.select().from(trafficSources).where(eq27(trafficSources.id, newId)).get();
30528
+ row = tx.select().from(trafficSources).where(eq28(trafficSources.id, newId)).get();
30219
30529
  }
30220
30530
  const existingSchedule = tx.select().from(schedules).where(
30221
- and21(
30222
- eq27(schedules.projectId, project.id),
30223
- eq27(schedules.kind, SchedulableRunKinds["traffic-sync"])
30531
+ and22(
30532
+ eq28(schedules.projectId, project.id),
30533
+ eq28(schedules.kind, SchedulableRunKinds["traffic-sync"])
30224
30534
  )
30225
30535
  ).get();
30226
30536
  let created = false;
@@ -30269,7 +30579,7 @@ async function trafficRoutes(app, opts) {
30269
30579
  });
30270
30580
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
30271
30581
  const project = resolveProject(app.db, request.params.name);
30272
- const sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, request.params.id)).get();
30582
+ const sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, request.params.id)).get();
30273
30583
  if (!sourceRow || sourceRow.projectId !== project.id) {
30274
30584
  throw notFound("Traffic source", request.params.id);
30275
30585
  }
@@ -30295,8 +30605,8 @@ async function trafficRoutes(app, opts) {
30295
30605
  const markFailed = (msg, errorCode) => {
30296
30606
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
30297
30607
  app.db.transaction((tx) => {
30298
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq27(runs.id, runId)).run();
30299
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq27(trafficSources.id, sourceRow.id)).run();
30608
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq28(runs.id, runId)).run();
30609
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq28(trafficSources.id, sourceRow.id)).run();
30300
30610
  });
30301
30611
  try {
30302
30612
  opts.onTrafficSynced?.({
@@ -30377,7 +30687,7 @@ async function trafficRoutes(app, opts) {
30377
30687
  }
30378
30688
  const credential = credentialStore.getConnection(project.name);
30379
30689
  if (!credential) {
30380
- app.db.delete(runs).where(eq27(runs.id, runId)).run();
30690
+ app.db.delete(runs).where(eq28(runs.id, runId)).run();
30381
30691
  throw validationError(
30382
30692
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
30383
30693
  );
@@ -30426,12 +30736,12 @@ async function trafficRoutes(app, opts) {
30426
30736
  auditAction = "traffic.vercel.synced";
30427
30737
  const credentialStore = opts.vercelTrafficCredentialStore;
30428
30738
  if (!credentialStore) {
30429
- app.db.delete(runs).where(eq27(runs.id, runId)).run();
30739
+ app.db.delete(runs).where(eq28(runs.id, runId)).run();
30430
30740
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
30431
30741
  }
30432
30742
  const credential = credentialStore.getConnection(project.name);
30433
30743
  if (!credential) {
30434
- app.db.delete(runs).where(eq27(runs.id, runId)).run();
30744
+ app.db.delete(runs).where(eq28(runs.id, runId)).run();
30435
30745
  throw validationError(
30436
30746
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
30437
30747
  );
@@ -30531,7 +30841,7 @@ async function trafficRoutes(app, opts) {
30531
30841
  let aiReferralHitsCount = 0;
30532
30842
  let unknownHitsCount = 0;
30533
30843
  app.db.transaction((tx) => {
30534
- const latestRow = tx.select().from(trafficSources).where(eq27(trafficSources.id, sourceRow.id)).get();
30844
+ const latestRow = tx.select().from(trafficSources).where(eq28(trafficSources.id, sourceRow.id)).get();
30535
30845
  const previousIds = latestRow.lastEventIds ?? [];
30536
30846
  const seenEventIds = new Set(previousIds);
30537
30847
  const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
@@ -30579,7 +30889,7 @@ async function trafficRoutes(app, opts) {
30579
30889
  crawlerEventsHourly.status
30580
30890
  ],
30581
30891
  set: {
30582
- hits: sql11`${crawlerEventsHourly.hits} + ${bucket.hits}`,
30892
+ hits: sql12`${crawlerEventsHourly.hits} + ${bucket.hits}`,
30583
30893
  sampledUserAgent: bucket.sampledUserAgent,
30584
30894
  updatedAt: finishedAt
30585
30895
  }
@@ -30612,7 +30922,7 @@ async function trafficRoutes(app, opts) {
30612
30922
  aiUserFetchEventsHourly.status
30613
30923
  ],
30614
30924
  set: {
30615
- hits: sql11`${aiUserFetchEventsHourly.hits} + ${bucket.hits}`,
30925
+ hits: sql12`${aiUserFetchEventsHourly.hits} + ${bucket.hits}`,
30616
30926
  sampledUserAgent: bucket.sampledUserAgent,
30617
30927
  updatedAt: finishedAt
30618
30928
  }
@@ -30647,7 +30957,7 @@ async function trafficRoutes(app, opts) {
30647
30957
  aiReferralEventsHourly.status
30648
30958
  ],
30649
30959
  set: {
30650
- sessionsOrHits: sql11`${aiReferralEventsHourly.sessionsOrHits} + ${bucket.hits}`,
30960
+ sessionsOrHits: sql12`${aiReferralEventsHourly.sessionsOrHits} + ${bucket.hits}`,
30651
30961
  updatedAt: finishedAt
30652
30962
  }
30653
30963
  }).run();
@@ -30699,8 +31009,8 @@ async function trafficRoutes(app, opts) {
30699
31009
  if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
30700
31010
  sourceUpdate.lastCursor = nextCursor ?? null;
30701
31011
  }
30702
- tx.update(trafficSources).set(sourceUpdate).where(eq27(trafficSources.id, sourceRow.id)).run();
30703
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq27(runs.id, runId)).run();
31012
+ tx.update(trafficSources).set(sourceUpdate).where(eq28(trafficSources.id, sourceRow.id)).run();
31013
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq28(runs.id, runId)).run();
30704
31014
  });
30705
31015
  writeAuditLog(app.db, {
30706
31016
  projectId: project.id,
@@ -30752,7 +31062,7 @@ async function trafficRoutes(app, opts) {
30752
31062
  });
30753
31063
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
30754
31064
  const project = resolveProject(app.db, request.params.name);
30755
- const sourceRow = app.db.select().from(trafficSources).where(eq27(trafficSources.id, request.params.id)).get();
31065
+ const sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, request.params.id)).get();
30756
31066
  if (!sourceRow || sourceRow.projectId !== project.id) {
30757
31067
  throw notFound("Traffic source", request.params.id);
30758
31068
  }
@@ -30938,10 +31248,10 @@ async function trafficRoutes(app, opts) {
30938
31248
  function buildSourceDetail(projectId, row, since) {
30939
31249
  const crawlerPathRows = app.db.select({
30940
31250
  pathNormalized: crawlerEventsHourly.pathNormalized,
30941
- hits: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
31251
+ hits: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
30942
31252
  }).from(crawlerEventsHourly).where(
30943
- and21(
30944
- eq27(crawlerEventsHourly.sourceId, row.id),
31253
+ and22(
31254
+ eq28(crawlerEventsHourly.sourceId, row.id),
30945
31255
  gte4(crawlerEventsHourly.tsHour, since)
30946
31256
  )
30947
31257
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
@@ -30949,29 +31259,29 @@ async function trafficRoutes(app, opts) {
30949
31259
  crawlerPathRows.map((r) => ({ pathNormalized: r.pathNormalized, hits: Number(r.hits) }))
30950
31260
  );
30951
31261
  const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
30952
- const aiUserFetchTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
30953
- and21(
30954
- eq27(aiUserFetchEventsHourly.sourceId, row.id),
31262
+ const aiUserFetchTotals = app.db.select({ total: sql12`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
31263
+ and22(
31264
+ eq28(aiUserFetchEventsHourly.sourceId, row.id),
30955
31265
  gte4(aiUserFetchEventsHourly.tsHour, since)
30956
31266
  )
30957
31267
  ).get();
30958
- const aiTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
30959
- and21(
30960
- eq27(aiReferralEventsHourly.sourceId, row.id),
31268
+ const aiTotals = app.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
31269
+ and22(
31270
+ eq28(aiReferralEventsHourly.sourceId, row.id),
30961
31271
  gte4(aiReferralEventsHourly.tsHour, since)
30962
31272
  )
30963
31273
  ).get();
30964
- const sampleTotals = app.db.select({ total: sql11`COUNT(*)` }).from(rawEventSamples).where(
30965
- and21(
30966
- eq27(rawEventSamples.sourceId, row.id),
31274
+ const sampleTotals = app.db.select({ total: sql12`COUNT(*)` }).from(rawEventSamples).where(
31275
+ and22(
31276
+ eq28(rawEventSamples.sourceId, row.id),
30967
31277
  gte4(rawEventSamples.ts, since)
30968
31278
  )
30969
31279
  ).get();
30970
31280
  const latestRun = app.db.select().from(runs).where(
30971
- and21(
30972
- eq27(runs.projectId, projectId),
30973
- eq27(runs.kind, RunKinds["traffic-sync"]),
30974
- eq27(runs.sourceId, row.id)
31281
+ and22(
31282
+ eq28(runs.projectId, projectId),
31283
+ eq28(runs.kind, RunKinds["traffic-sync"]),
31284
+ eq28(runs.sourceId, row.id)
30975
31285
  )
30976
31286
  ).orderBy(desc15(runs.startedAt)).limit(1).get();
30977
31287
  return {
@@ -31002,7 +31312,7 @@ async function trafficRoutes(app, opts) {
31002
31312
  "`advanceToNow` must be `true`. There is no implicit reset."
31003
31313
  );
31004
31314
  }
31005
- const sourceRow = app.db.select().from(trafficSources).where(and21(eq27(trafficSources.projectId, project.id), eq27(trafficSources.id, request.params.id))).get();
31315
+ const sourceRow = app.db.select().from(trafficSources).where(and22(eq28(trafficSources.projectId, project.id), eq28(trafficSources.id, request.params.id))).get();
31006
31316
  if (!sourceRow) {
31007
31317
  throw notFound("traffic source", request.params.id);
31008
31318
  }
@@ -31019,7 +31329,7 @@ async function trafficRoutes(app, opts) {
31019
31329
  status: TrafficSourceStatuses.connected,
31020
31330
  lastError: null,
31021
31331
  updatedAt: now
31022
- }).where(eq27(trafficSources.id, sourceRow.id)).run();
31332
+ }).where(eq28(trafficSources.id, sourceRow.id)).run();
31023
31333
  writeAuditLog(tx, auditFromRequest(request, {
31024
31334
  projectId: project.id,
31025
31335
  actor: "api",
@@ -31027,20 +31337,20 @@ async function trafficRoutes(app, opts) {
31027
31337
  entityType: "traffic_source",
31028
31338
  entityId: sourceRow.id
31029
31339
  }));
31030
- updatedRow = tx.select().from(trafficSources).where(eq27(trafficSources.id, sourceRow.id)).get();
31340
+ updatedRow = tx.select().from(trafficSources).where(eq28(trafficSources.id, sourceRow.id)).get();
31031
31341
  });
31032
31342
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
31033
31343
  });
31034
31344
  app.get("/projects/:name/traffic/sources", async (request) => {
31035
31345
  const project = resolveProject(app.db, request.params.name);
31036
- const rows = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
31346
+ const rows = app.db.select().from(trafficSources).where(eq28(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
31037
31347
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
31038
31348
  const response = { sources };
31039
31349
  return response;
31040
31350
  });
31041
31351
  app.get("/projects/:name/traffic/status", async (request) => {
31042
31352
  const project = resolveProject(app.db, request.params.name);
31043
- const rows = app.db.select().from(trafficSources).where(eq27(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
31353
+ const rows = app.db.select().from(trafficSources).where(eq28(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
31044
31354
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
31045
31355
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
31046
31356
  const response = { sources };
@@ -31050,7 +31360,7 @@ async function trafficRoutes(app, opts) {
31050
31360
  "/projects/:name/traffic/sources/:id",
31051
31361
  async (request) => {
31052
31362
  const project = resolveProject(app.db, request.params.name);
31053
- const row = app.db.select().from(trafficSources).where(eq27(trafficSources.id, request.params.id)).get();
31363
+ const row = app.db.select().from(trafficSources).where(eq28(trafficSources.id, request.params.id)).get();
31054
31364
  if (!row || row.projectId !== project.id) {
31055
31365
  throw notFound("Traffic source", request.params.id);
31056
31366
  }
@@ -31102,15 +31412,15 @@ async function trafficRoutes(app, opts) {
31102
31412
  let aiReferralTotal = 0;
31103
31413
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
31104
31414
  const crawlerFilters = [
31105
- eq27(crawlerEventsHourly.projectId, project.id),
31415
+ eq28(crawlerEventsHourly.projectId, project.id),
31106
31416
  gte4(crawlerEventsHourly.tsHour, sinceIso),
31107
31417
  lte3(crawlerEventsHourly.tsHour, untilIso)
31108
31418
  ];
31109
- if (sourceIdParam) crawlerFilters.push(eq27(crawlerEventsHourly.sourceId, sourceIdParam));
31110
- const crawlerWhere = and21(...crawlerFilters);
31419
+ if (sourceIdParam) crawlerFilters.push(eq28(crawlerEventsHourly.sourceId, sourceIdParam));
31420
+ const crawlerWhere = and22(...crawlerFilters);
31111
31421
  const pathTotals = app.db.select({
31112
31422
  pathNormalized: crawlerEventsHourly.pathNormalized,
31113
- hits: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
31423
+ hits: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
31114
31424
  }).from(crawlerEventsHourly).where(crawlerWhere).groupBy(crawlerEventsHourly.pathNormalized).all();
31115
31425
  crawlerSegments = segmentCrawlerHits(
31116
31426
  pathTotals.map((r) => ({ pathNormalized: r.pathNormalized, hits: Number(r.hits) }))
@@ -31134,13 +31444,13 @@ async function trafficRoutes(app, opts) {
31134
31444
  }
31135
31445
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
31136
31446
  const userFetchFilters = [
31137
- eq27(aiUserFetchEventsHourly.projectId, project.id),
31447
+ eq28(aiUserFetchEventsHourly.projectId, project.id),
31138
31448
  gte4(aiUserFetchEventsHourly.tsHour, sinceIso),
31139
31449
  lte3(aiUserFetchEventsHourly.tsHour, untilIso)
31140
31450
  ];
31141
- if (sourceIdParam) userFetchFilters.push(eq27(aiUserFetchEventsHourly.sourceId, sourceIdParam));
31142
- const userFetchWhere = and21(...userFetchFilters);
31143
- const total = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
31451
+ if (sourceIdParam) userFetchFilters.push(eq28(aiUserFetchEventsHourly.sourceId, sourceIdParam));
31452
+ const userFetchWhere = and22(...userFetchFilters);
31453
+ const total = app.db.select({ total: sql12`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
31144
31454
  aiUserFetchTotal = Number(total?.total ?? 0);
31145
31455
  const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc15(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
31146
31456
  for (const r of rows) {
@@ -31159,13 +31469,13 @@ async function trafficRoutes(app, opts) {
31159
31469
  }
31160
31470
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
31161
31471
  const aiFilters = [
31162
- eq27(aiReferralEventsHourly.projectId, project.id),
31472
+ eq28(aiReferralEventsHourly.projectId, project.id),
31163
31473
  gte4(aiReferralEventsHourly.tsHour, sinceIso),
31164
31474
  lte3(aiReferralEventsHourly.tsHour, untilIso)
31165
31475
  ];
31166
- if (sourceIdParam) aiFilters.push(eq27(aiReferralEventsHourly.sourceId, sourceIdParam));
31167
- const aiWhere = and21(...aiFilters);
31168
- const total = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
31476
+ if (sourceIdParam) aiFilters.push(eq28(aiReferralEventsHourly.sourceId, sourceIdParam));
31477
+ const aiWhere = and22(...aiFilters);
31478
+ const total = app.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
31169
31479
  aiReferralTotal = Number(total?.total ?? 0);
31170
31480
  const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc15(aiReferralEventsHourly.tsHour)).limit(limit).all();
31171
31481
  for (const r of rows) {
@@ -31371,7 +31681,7 @@ function readInstalledManifest(skillDir) {
31371
31681
  var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck];
31372
31682
 
31373
31683
  // ../api-routes/src/doctor/checks/backlinks.ts
31374
- import { and as and22, eq as eq28 } from "drizzle-orm";
31684
+ import { and as and23, eq as eq29 } from "drizzle-orm";
31375
31685
  function skippedNoProject() {
31376
31686
  return {
31377
31687
  status: CheckStatuses.skipped,
@@ -31387,8 +31697,8 @@ var BACKLINKS_CHECKS = [
31387
31697
  title: "Backlinks source connected",
31388
31698
  run: (ctx) => {
31389
31699
  if (!ctx.project) return skippedNoProject();
31390
- const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq28(projects.id, ctx.project.id)).get();
31391
- const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
31700
+ const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq29(projects.id, ctx.project.id)).get();
31701
+ const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq29(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
31392
31702
  const ccConnected = projectRow?.autoExtract === true && !!readySync;
31393
31703
  const bingConnected = !!ctx.bingConnectionStore?.getConnection(ctx.project.canonicalDomain);
31394
31704
  const connected = [];
@@ -31403,9 +31713,9 @@ var BACKLINKS_CHECKS = [
31403
31713
  details: { commoncrawl: ccConnected, bingWebmaster: bingConnected }
31404
31714
  };
31405
31715
  }
31406
- const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and22(
31407
- eq28(backlinkSummaries.projectId, ctx.project.id),
31408
- eq28(backlinkSummaries.source, BacklinkSources.commoncrawl)
31716
+ const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and23(
31717
+ eq29(backlinkSummaries.projectId, ctx.project.id),
31718
+ eq29(backlinkSummaries.source, BacklinkSources.commoncrawl)
31409
31719
  )).limit(1).get() : false;
31410
31720
  return {
31411
31721
  status: CheckStatuses.ok,
@@ -31565,7 +31875,7 @@ var BING_AUTH_CHECKS = [
31565
31875
  ];
31566
31876
 
31567
31877
  // ../api-routes/src/doctor/checks/content.ts
31568
- import { eq as eq29 } from "drizzle-orm";
31878
+ import { eq as eq30 } from "drizzle-orm";
31569
31879
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
31570
31880
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
31571
31881
  function skippedNoProject2() {
@@ -31578,7 +31888,7 @@ function skippedNoProject2() {
31578
31888
  }
31579
31889
  function loadProject(ctx) {
31580
31890
  if (!ctx.project) return null;
31581
- return ctx.db.select().from(projects).where(eq29(projects.id, ctx.project.id)).get() ?? null;
31891
+ return ctx.db.select().from(projects).where(eq30(projects.id, ctx.project.id)).get() ?? null;
31582
31892
  }
31583
31893
  function percent(value) {
31584
31894
  return Math.round(value * 100);
@@ -31670,7 +31980,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
31670
31980
  );
31671
31981
 
31672
31982
  // ../api-routes/src/doctor/checks/ads.ts
31673
- import { eq as eq30 } from "drizzle-orm";
31983
+ import { eq as eq31 } from "drizzle-orm";
31674
31984
  var RECENT_SYNC_WARN_DAYS = 7;
31675
31985
  var RECENT_SYNC_FAIL_DAYS = 30;
31676
31986
  var adsConnectionCheck = {
@@ -31687,7 +31997,7 @@ var adsConnectionCheck = {
31687
31997
  remediation: null
31688
31998
  };
31689
31999
  }
31690
- const row = ctx.db.select().from(adsConnections).where(eq30(adsConnections.projectId, ctx.project.id)).get();
32000
+ const row = ctx.db.select().from(adsConnections).where(eq31(adsConnections.projectId, ctx.project.id)).get();
31691
32001
  if (!row) {
31692
32002
  return {
31693
32003
  status: CheckStatuses.skipped,
@@ -31737,7 +32047,7 @@ var adsRecentSyncCheck = {
31737
32047
  remediation: null
31738
32048
  };
31739
32049
  }
31740
- const row = ctx.db.select().from(adsConnections).where(eq30(adsConnections.projectId, ctx.project.id)).get();
32050
+ const row = ctx.db.select().from(adsConnections).where(eq31(adsConnections.projectId, ctx.project.id)).get();
31741
32051
  if (!row) {
31742
32052
  return {
31743
32053
  status: CheckStatuses.skipped,
@@ -31928,7 +32238,7 @@ var ga4ConnectionCheck = {
31928
32238
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
31929
32239
 
31930
32240
  // ../api-routes/src/doctor/checks/gbp-auth.ts
31931
- import { and as and23, eq as eq31 } from "drizzle-orm";
32241
+ import { and as and24, eq as eq32 } from "drizzle-orm";
31932
32242
  var RECENT_SYNC_WARN_DAYS2 = 7;
31933
32243
  var RECENT_SYNC_FAIL_DAYS2 = 30;
31934
32244
  function skippedNoProject3() {
@@ -32161,7 +32471,7 @@ var recentSyncCheck = {
32161
32471
  title: "GBP recent sync",
32162
32472
  run: (ctx) => {
32163
32473
  if (!ctx.project) return skippedNoProject3();
32164
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and23(eq31(gbpLocations.projectId, ctx.project.id), eq31(gbpLocations.selected, true))).all();
32474
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and24(eq32(gbpLocations.projectId, ctx.project.id), eq32(gbpLocations.selected, true))).all();
32165
32475
  if (selected.length === 0) {
32166
32476
  return {
32167
32477
  status: CheckStatuses.skipped,
@@ -32221,7 +32531,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
32221
32531
  );
32222
32532
 
32223
32533
  // ../api-routes/src/doctor/checks/places.ts
32224
- import { eq as eq32 } from "drizzle-orm";
32534
+ import { eq as eq33 } from "drizzle-orm";
32225
32535
  var apiKeyCheck = {
32226
32536
  id: "gbp.places.api-key",
32227
32537
  category: CheckCategories.auth,
@@ -32266,7 +32576,7 @@ var apiKeyCheck = {
32266
32576
  details: { tier: cfg.tier }
32267
32577
  };
32268
32578
  }
32269
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq32(gbpLocations.projectId, ctx.project.id)).all();
32579
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq33(gbpLocations.projectId, ctx.project.id)).all();
32270
32580
  const selected = rows.filter((r) => r.selected);
32271
32581
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
32272
32582
  const details = {
@@ -32763,7 +33073,7 @@ var RUNTIME_STATE_CHECKS = [
32763
33073
  ];
32764
33074
 
32765
33075
  // ../api-routes/src/doctor/checks/traffic-source.ts
32766
- import { and as and24, eq as eq33, gte as gte5, ne as ne4, sql as sql12 } from "drizzle-orm";
33076
+ import { and as and25, eq as eq34, gte as gte5, ne as ne4, sql as sql13 } from "drizzle-orm";
32767
33077
  var RECENT_DATA_WARN_DAYS = 7;
32768
33078
  var RECENT_DATA_FAIL_DAYS = 30;
32769
33079
  function skippedNoProject5() {
@@ -32777,8 +33087,8 @@ function skippedNoProject5() {
32777
33087
  function loadProbes(ctx) {
32778
33088
  if (!ctx.project) return [];
32779
33089
  const rows = ctx.db.select().from(trafficSources).where(
32780
- and24(
32781
- eq33(trafficSources.projectId, ctx.project.id),
33090
+ and25(
33091
+ eq34(trafficSources.projectId, ctx.project.id),
32782
33092
  ne4(trafficSources.status, TrafficSourceStatuses.archived)
32783
33093
  )
32784
33094
  ).all();
@@ -32857,17 +33167,17 @@ var recentDataCheck = {
32857
33167
  const warnCutoff = new Date(now.getTime() - RECENT_DATA_WARN_DAYS * 24 * 60 * 6e4).toISOString();
32858
33168
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
32859
33169
  const recentCrawlers = Number(
32860
- ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
32861
- and24(
32862
- eq33(crawlerEventsHourly.projectId, ctx.project.id),
33170
+ ctx.db.select({ total: sql13`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
33171
+ and25(
33172
+ eq34(crawlerEventsHourly.projectId, ctx.project.id),
32863
33173
  gte5(crawlerEventsHourly.tsHour, warnCutoff)
32864
33174
  )
32865
33175
  ).get()?.total ?? 0
32866
33176
  );
32867
33177
  const recentReferrals = Number(
32868
- ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
32869
- and24(
32870
- eq33(aiReferralEventsHourly.projectId, ctx.project.id),
33178
+ ctx.db.select({ total: sql13`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
33179
+ and25(
33180
+ eq34(aiReferralEventsHourly.projectId, ctx.project.id),
32871
33181
  gte5(aiReferralEventsHourly.tsHour, warnCutoff)
32872
33182
  )
32873
33183
  ).get()?.total ?? 0
@@ -32881,17 +33191,17 @@ var recentDataCheck = {
32881
33191
  };
32882
33192
  }
32883
33193
  const olderCrawlers = Number(
32884
- ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
32885
- and24(
32886
- eq33(crawlerEventsHourly.projectId, ctx.project.id),
33194
+ ctx.db.select({ total: sql13`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
33195
+ and25(
33196
+ eq34(crawlerEventsHourly.projectId, ctx.project.id),
32887
33197
  gte5(crawlerEventsHourly.tsHour, failCutoff)
32888
33198
  )
32889
33199
  ).get()?.total ?? 0
32890
33200
  );
32891
33201
  const olderReferrals = Number(
32892
- ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
32893
- and24(
32894
- eq33(aiReferralEventsHourly.projectId, ctx.project.id),
33202
+ ctx.db.select({ total: sql13`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
33203
+ and25(
33204
+ eq34(aiReferralEventsHourly.projectId, ctx.project.id),
32895
33205
  gte5(aiReferralEventsHourly.tsHour, failCutoff)
32896
33206
  )
32897
33207
  ).get()?.total ?? 0
@@ -33307,7 +33617,7 @@ async function doctorRoutes(app, opts) {
33307
33617
 
33308
33618
  // ../api-routes/src/discovery/routes.ts
33309
33619
  import crypto27 from "crypto";
33310
- import { and as and25, desc as desc16, eq as eq34, gte as gte6, inArray as inArray12 } from "drizzle-orm";
33620
+ import { and as and26, desc as desc16, eq as eq35, gte as gte6, inArray as inArray12 } from "drizzle-orm";
33311
33621
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
33312
33622
  async function discoveryRoutes(app, opts) {
33313
33623
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -33339,9 +33649,9 @@ async function discoveryRoutes(app, opts) {
33339
33649
  const now = (/* @__PURE__ */ new Date()).toISOString();
33340
33650
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
33341
33651
  const decision = app.db.transaction((tx) => {
33342
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and25(
33343
- eq34(discoverySessions.projectId, project.id),
33344
- eq34(discoverySessions.icpDescription, icpDescription),
33652
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and26(
33653
+ eq35(discoverySessions.projectId, project.id),
33654
+ eq35(discoverySessions.icpDescription, icpDescription),
33345
33655
  inArray12(discoverySessions.status, [
33346
33656
  DiscoverySessionStatuses.queued,
33347
33657
  DiscoverySessionStatuses.seeding,
@@ -33411,7 +33721,7 @@ async function discoveryRoutes(app, opts) {
33411
33721
  const project = resolveProject(app.db, request.params.name);
33412
33722
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
33413
33723
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
33414
- const rows = app.db.select().from(discoverySessions).where(eq34(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
33724
+ const rows = app.db.select().from(discoverySessions).where(eq35(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
33415
33725
  return reply.send(rows.map(serializeSession));
33416
33726
  }
33417
33727
  );
@@ -33419,11 +33729,11 @@ async function discoveryRoutes(app, opts) {
33419
33729
  "/projects/:name/discover/sessions/:id",
33420
33730
  async (request, reply) => {
33421
33731
  const project = resolveProject(app.db, request.params.name);
33422
- const session = app.db.select().from(discoverySessions).where(eq34(discoverySessions.id, request.params.id)).get();
33732
+ const session = app.db.select().from(discoverySessions).where(eq35(discoverySessions.id, request.params.id)).get();
33423
33733
  if (!session || session.projectId !== project.id) {
33424
33734
  throw notFound("Discovery session", request.params.id);
33425
33735
  }
33426
- const probeRows = app.db.select().from(discoveryProbes).where(eq34(discoveryProbes.sessionId, session.id)).all();
33736
+ const probeRows = app.db.select().from(discoveryProbes).where(eq35(discoveryProbes.sessionId, session.id)).all();
33427
33737
  const detail = {
33428
33738
  ...serializeSession(session),
33429
33739
  probes: probeRows.map(serializeProbe)
@@ -33435,7 +33745,7 @@ async function discoveryRoutes(app, opts) {
33435
33745
  "/projects/:name/discover/sessions/:id/harvest",
33436
33746
  async (request, reply) => {
33437
33747
  const project = resolveProject(app.db, request.params.name);
33438
- const session = app.db.select().from(discoverySessions).where(eq34(discoverySessions.id, request.params.id)).get();
33748
+ const session = app.db.select().from(discoverySessions).where(eq35(discoverySessions.id, request.params.id)).get();
33439
33749
  if (!session || session.projectId !== project.id) {
33440
33750
  throw notFound("Discovery session", request.params.id);
33441
33751
  }
@@ -33443,7 +33753,7 @@ async function discoveryRoutes(app, opts) {
33443
33753
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
33444
33754
  const applyAnchor = request.query.anchor !== "false";
33445
33755
  const provider = session.seedProvider ?? "gemini";
33446
- const probeRows = app.db.select().from(discoveryProbes).where(eq34(discoveryProbes.sessionId, session.id)).all();
33756
+ const probeRows = app.db.select().from(discoveryProbes).where(eq35(discoveryProbes.sessionId, session.id)).all();
33447
33757
  const extract = opts.harvestSearchQueries;
33448
33758
  const probesWithQueries = probeRows.map((row) => {
33449
33759
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -33454,7 +33764,7 @@ async function discoveryRoutes(app, opts) {
33454
33764
  return { searchQueries: [] };
33455
33765
  }
33456
33766
  });
33457
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq34(queries.projectId, project.id)).all().map((r) => r.query);
33767
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq35(queries.projectId, project.id)).all().map((r) => r.query);
33458
33768
  const anchorTerms = buildHarvestAnchorTerms(
33459
33769
  [session.icpDescription ?? "", ...trackedQueries],
33460
33770
  effectiveDomains(project)
@@ -33501,12 +33811,12 @@ async function discoveryRoutes(app, opts) {
33501
33811
  "/projects/:name/discover/sessions/:id/promote",
33502
33812
  async (request, reply) => {
33503
33813
  const project = resolveProject(app.db, request.params.name);
33504
- const session = app.db.select().from(discoverySessions).where(eq34(discoverySessions.id, request.params.id)).get();
33814
+ const session = app.db.select().from(discoverySessions).where(eq35(discoverySessions.id, request.params.id)).get();
33505
33815
  if (!session || session.projectId !== project.id) {
33506
33816
  throw notFound("Discovery session", request.params.id);
33507
33817
  }
33508
- const probeRows = app.db.select().from(discoveryProbes).where(eq34(discoveryProbes.sessionId, session.id)).all();
33509
- const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq34(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
33818
+ const probeRows = app.db.select().from(discoveryProbes).where(eq35(discoveryProbes.sessionId, session.id)).all();
33819
+ const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq35(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
33510
33820
  const seenCompetitors = new Set(existingCompetitors);
33511
33821
  const cited = /* @__PURE__ */ new Set();
33512
33822
  const aspirational = /* @__PURE__ */ new Set();
@@ -33535,7 +33845,7 @@ async function discoveryRoutes(app, opts) {
33535
33845
  );
33536
33846
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
33537
33847
  const project = resolveProject(app.db, request.params.name);
33538
- const session = app.db.select().from(discoverySessions).where(eq34(discoverySessions.id, request.params.id)).get();
33848
+ const session = app.db.select().from(discoverySessions).where(eq35(discoverySessions.id, request.params.id)).get();
33539
33849
  if (!session || session.projectId !== project.id) {
33540
33850
  throw notFound("Discovery session", request.params.id);
33541
33851
  }
@@ -33558,7 +33868,7 @@ async function discoveryRoutes(app, opts) {
33558
33868
  const bucketSet = new Set(buckets);
33559
33869
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
33560
33870
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
33561
- const probeRows = app.db.select().from(discoveryProbes).where(eq34(discoveryProbes.sessionId, session.id)).all();
33871
+ const probeRows = app.db.select().from(discoveryProbes).where(eq35(discoveryProbes.sessionId, session.id)).all();
33562
33872
  const candidateQueries = /* @__PURE__ */ new Set();
33563
33873
  for (const probe of probeRows) {
33564
33874
  if (!probe.bucket) continue;
@@ -33566,7 +33876,7 @@ async function discoveryRoutes(app, opts) {
33566
33876
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
33567
33877
  }
33568
33878
  const existingQueries = new Set(
33569
- app.db.select({ query: queries.query }).from(queries).where(eq34(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
33879
+ app.db.select({ query: queries.query }).from(queries).where(eq35(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
33570
33880
  );
33571
33881
  const promotedQueries = [];
33572
33882
  const skippedQueries = [];
@@ -33582,7 +33892,7 @@ async function discoveryRoutes(app, opts) {
33582
33892
  const skippedCompetitors = [];
33583
33893
  if (includeCompetitors) {
33584
33894
  const existingCompetitors = new Set(
33585
- app.db.select({ domain: competitors.domain }).from(competitors).where(eq34(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
33895
+ app.db.select({ domain: competitors.domain }).from(competitors).where(eq35(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
33586
33896
  );
33587
33897
  const competitorMap = parseCompetitorMap(session.competitorMap);
33588
33898
  for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
@@ -33689,7 +33999,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
33689
33999
 
33690
34000
  // ../api-routes/src/discovery/orchestrate.ts
33691
34001
  import crypto28 from "crypto";
33692
- import { eq as eq35 } from "drizzle-orm";
34002
+ import { eq as eq36 } from "drizzle-orm";
33693
34003
  var DEFAULT_MAX_PROBES = 100;
33694
34004
  var ABSOLUTE_MAX_PROBES = 500;
33695
34005
  function classifyProbeBucket(input) {
@@ -33743,7 +34053,7 @@ async function executeDiscovery(opts) {
33743
34053
  status: DiscoverySessionStatuses.seeding,
33744
34054
  dedupThreshold,
33745
34055
  startedAt
33746
- }).where(eq35(discoverySessions.id, opts.sessionId)).run();
34056
+ }).where(eq36(discoverySessions.id, opts.sessionId)).run();
33747
34057
  const seedResult = await opts.deps.seed({
33748
34058
  project: opts.project,
33749
34059
  icpDescription: opts.icpDescription,
@@ -33769,7 +34079,7 @@ async function executeDiscovery(opts) {
33769
34079
  seedCountRaw,
33770
34080
  seedCount,
33771
34081
  warning
33772
- }).where(eq35(discoverySessions.id, opts.sessionId)).run();
34082
+ }).where(eq36(discoverySessions.id, opts.sessionId)).run();
33773
34083
  const probeRows = [];
33774
34084
  const buckets = { cited: 0, aspirational: 0, "wasted-surface": 0 };
33775
34085
  for (const query of probedCanonicals) {
@@ -33810,7 +34120,7 @@ async function executeDiscovery(opts) {
33810
34120
  wastedCount: buckets["wasted-surface"],
33811
34121
  competitorMap,
33812
34122
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
33813
- }).where(eq35(discoverySessions.id, opts.sessionId)).run();
34123
+ }).where(eq36(discoverySessions.id, opts.sessionId)).run();
33814
34124
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
33815
34125
  return {
33816
34126
  buckets,
@@ -33850,7 +34160,7 @@ function markSessionFailed(db, sessionId, error) {
33850
34160
  status: DiscoverySessionStatuses.failed,
33851
34161
  error,
33852
34162
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
33853
- }).where(eq35(discoverySessions.id, sessionId)).run();
34163
+ }).where(eq36(discoverySessions.id, sessionId)).run();
33854
34164
  }
33855
34165
  function dedupeStrings(input) {
33856
34166
  const seen = /* @__PURE__ */ new Set();
@@ -33868,7 +34178,7 @@ function dedupeStrings(input) {
33868
34178
 
33869
34179
  // ../api-routes/src/technical-aeo.ts
33870
34180
  import crypto29 from "crypto";
33871
- import { and as and26, asc as asc4, count, desc as desc17, eq as eq36, inArray as inArray13 } from "drizzle-orm";
34181
+ import { and as and27, asc as asc5, count, desc as desc17, eq as eq37, inArray as inArray13 } from "drizzle-orm";
33872
34182
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
33873
34183
  function emptyScore(projectName) {
33874
34184
  return {
@@ -33900,9 +34210,9 @@ function parsePositiveInt(value, fallback, max) {
33900
34210
  async function technicalAeoRoutes(app, opts) {
33901
34211
  app.get("/projects/:name/technical-aeo", async (request) => {
33902
34212
  const project = resolveProject(app.db, request.params.name);
33903
- const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq36(siteAuditSnapshots.runId, runs.id)).where(and26(
33904
- eq36(siteAuditSnapshots.projectId, project.id),
33905
- eq36(runs.kind, RunKinds["site-audit"]),
34213
+ const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(and27(
34214
+ eq37(siteAuditSnapshots.projectId, project.id),
34215
+ eq37(runs.kind, RunKinds["site-audit"]),
33906
34216
  inArray13(runs.status, SURFACEABLE_STATUSES),
33907
34217
  notProbeRun()
33908
34218
  )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(2).all();
@@ -33935,9 +34245,9 @@ async function technicalAeoRoutes(app, opts) {
33935
34245
  });
33936
34246
  app.get("/projects/:name/technical-aeo/pages", async (request) => {
33937
34247
  const project = resolveProject(app.db, request.params.name);
33938
- const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq36(siteAuditSnapshots.runId, runs.id)).where(and26(
33939
- eq36(siteAuditSnapshots.projectId, project.id),
33940
- eq36(runs.kind, RunKinds["site-audit"]),
34248
+ const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(and27(
34249
+ eq37(siteAuditSnapshots.projectId, project.id),
34250
+ eq37(runs.kind, RunKinds["site-audit"]),
33941
34251
  inArray13(runs.status, SURFACEABLE_STATUSES),
33942
34252
  notProbeRun()
33943
34253
  )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get();
@@ -33945,14 +34255,14 @@ async function technicalAeoRoutes(app, opts) {
33945
34255
  return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
33946
34256
  }
33947
34257
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
33948
- const conds = [eq36(siteAuditPages.runId, latest.runId)];
33949
- if (statusFilter) conds.push(eq36(siteAuditPages.status, statusFilter));
33950
- const where = and26(...conds);
34258
+ const conds = [eq37(siteAuditPages.runId, latest.runId)];
34259
+ if (statusFilter) conds.push(eq37(siteAuditPages.status, statusFilter));
34260
+ const where = and27(...conds);
33951
34261
  const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
33952
34262
  const total = totalRow?.value ?? 0;
33953
34263
  const limit = parsePositiveInt(request.query.limit, 100, 500);
33954
34264
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
33955
- const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ? asc4(siteAuditPages.url) : asc4(siteAuditPages.overallScore);
34265
+ const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ? asc5(siteAuditPages.url) : asc5(siteAuditPages.overallScore);
33956
34266
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
33957
34267
  const pages = rows.map((row) => ({
33958
34268
  url: row.url,
@@ -33971,9 +34281,9 @@ async function technicalAeoRoutes(app, opts) {
33971
34281
  auditedAt: siteAuditSnapshots.auditedAt,
33972
34282
  aggregateScore: siteAuditSnapshots.aggregateScore,
33973
34283
  pagesAudited: siteAuditSnapshots.pagesAudited
33974
- }).from(siteAuditSnapshots).innerJoin(runs, eq36(siteAuditSnapshots.runId, runs.id)).where(and26(
33975
- eq36(siteAuditSnapshots.projectId, project.id),
33976
- eq36(runs.kind, RunKinds["site-audit"]),
34284
+ }).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(and27(
34285
+ eq37(siteAuditSnapshots.projectId, project.id),
34286
+ eq37(runs.kind, RunKinds["site-audit"]),
33977
34287
  inArray13(runs.status, SURFACEABLE_STATUSES),
33978
34288
  notProbeRun()
33979
34289
  )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(limit).all();
@@ -33985,9 +34295,9 @@ async function technicalAeoRoutes(app, opts) {
33985
34295
  if (!parsed.success) {
33986
34296
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
33987
34297
  }
33988
- const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and26(
33989
- eq36(runs.projectId, project.id),
33990
- eq36(runs.kind, RunKinds["site-audit"]),
34298
+ const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and27(
34299
+ eq37(runs.projectId, project.id),
34300
+ eq37(runs.kind, RunKinds["site-audit"]),
33991
34301
  inArray13(runs.status, [RunStatuses.queued, RunStatuses.running])
33992
34302
  )).get();
33993
34303
  if (existing) {
@@ -34578,9 +34888,9 @@ var IntelligenceService = class {
34578
34888
  */
34579
34889
  analyzeAndPersist(runId, projectId) {
34580
34890
  const recentRuns = this.db.select().from(runs).where(
34581
- and27(
34582
- eq37(runs.projectId, projectId),
34583
- or5(eq37(runs.status, "completed"), eq37(runs.status, "partial")),
34891
+ and28(
34892
+ eq38(runs.projectId, projectId),
34893
+ or5(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
34584
34894
  // Defensive: RunCoordinator already skips probes before this is
34585
34895
  // called, but if a future call site invokes analyzeAndPersist
34586
34896
  // directly for a probe, probes still must not pollute the
@@ -34662,7 +34972,7 @@ var IntelligenceService = class {
34662
34972
  * Returns the persisted insights so the coordinator can count critical/high.
34663
34973
  */
34664
34974
  analyzeAndPersistGbp(runId, projectId) {
34665
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq37(runs.id, runId)).get();
34975
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq38(runs.id, runId)).get();
34666
34976
  if (!runRow) {
34667
34977
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
34668
34978
  this.persistGbpInsights(runId, projectId, [], []);
@@ -34670,9 +34980,9 @@ var IntelligenceService = class {
34670
34980
  }
34671
34981
  const windowStart = runRow.startedAt ?? runRow.createdAt;
34672
34982
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
34673
- const selected = this.db.select().from(gbpLocations).where(and27(
34674
- eq37(gbpLocations.projectId, projectId),
34675
- eq37(gbpLocations.selected, true),
34983
+ const selected = this.db.select().from(gbpLocations).where(and28(
34984
+ eq38(gbpLocations.projectId, projectId),
34985
+ eq38(gbpLocations.selected, true),
34676
34986
  gte7(gbpLocations.syncedAt, windowStart),
34677
34987
  lte4(gbpLocations.syncedAt, windowEnd)
34678
34988
  )).all();
@@ -34707,12 +35017,12 @@ var IntelligenceService = class {
34707
35017
  }
34708
35018
  /** Build the per-location signal bundle the GBP analyzer consumes. */
34709
35019
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
34710
- const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and27(eq37(gbpDailyMetrics.projectId, projectId), eq37(gbpDailyMetrics.locationName, locationName))).all();
34711
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and27(eq37(gbpPlaceActions.projectId, projectId), eq37(gbpPlaceActions.locationName, locationName))).all();
34712
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and27(eq37(gbpLodgingSnapshots.projectId, projectId), eq37(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc18(gbpLodgingSnapshots.syncedAt)).limit(1).get();
34713
- const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and27(eq37(gbpLocations.projectId, projectId), eq37(gbpLocations.locationName, locationName))).get();
35020
+ const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and28(eq38(gbpDailyMetrics.projectId, projectId), eq38(gbpDailyMetrics.locationName, locationName))).all();
35021
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and28(eq38(gbpPlaceActions.projectId, projectId), eq38(gbpPlaceActions.locationName, locationName))).all();
35022
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and28(eq38(gbpLodgingSnapshots.projectId, projectId), eq38(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc18(gbpLodgingSnapshots.syncedAt)).limit(1).get();
35023
+ const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and28(eq38(gbpLocations.projectId, projectId), eq38(gbpLocations.locationName, locationName))).get();
34714
35024
  const descriptionMissing = !(ownerRow?.description ?? "").trim();
34715
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and27(eq37(gbpPlaceDetails.projectId, projectId), eq37(gbpPlaceDetails.locationName, locationName))).orderBy(desc18(gbpPlaceDetails.syncedAt)).limit(1).get();
35025
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and28(eq38(gbpPlaceDetails.projectId, projectId), eq38(gbpPlaceDetails.locationName, locationName))).orderBy(desc18(gbpPlaceDetails.syncedAt)).limit(1).get();
34716
35026
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
34717
35027
  const summary = buildGbpSummary({
34718
35028
  locationName,
@@ -34748,7 +35058,7 @@ var IntelligenceService = class {
34748
35058
  /** Build the month-over-month keyword series for a location from the
34749
35059
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
34750
35060
  buildGbpKeywordTrend(projectId, locationName) {
34751
- const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and27(eq37(gbpKeywordMonthly.projectId, projectId), eq37(gbpKeywordMonthly.locationName, locationName))).all();
35061
+ const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and28(eq38(gbpKeywordMonthly.projectId, projectId), eq38(gbpKeywordMonthly.locationName, locationName))).all();
34752
35062
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
34753
35063
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
34754
35064
  const recentMonth = months[0] ?? null;
@@ -34779,7 +35089,7 @@ var IntelligenceService = class {
34779
35089
  */
34780
35090
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
34781
35091
  const covered = new Set(coveredLocationNames);
34782
- const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and27(eq37(insights.projectId, projectId), eq37(insights.provider, GBP_INSIGHT_PROVIDER))).all();
35092
+ const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and28(eq38(insights.projectId, projectId), eq38(insights.provider, GBP_INSIGHT_PROVIDER))).all();
34783
35093
  const staleIds = [];
34784
35094
  const dismissedSlots = /* @__PURE__ */ new Set();
34785
35095
  for (const row of existing) {
@@ -34790,7 +35100,7 @@ var IntelligenceService = class {
34790
35100
  }
34791
35101
  this.db.transaction((tx) => {
34792
35102
  for (const id of staleIds) {
34793
- tx.delete(insights).where(eq37(insights.id, id)).run();
35103
+ tx.delete(insights).where(eq38(insights.id, id)).run();
34794
35104
  }
34795
35105
  for (const insight of gbpInsights) {
34796
35106
  const parsed = parseGbpInsightId(insight.id);
@@ -34868,7 +35178,7 @@ var IntelligenceService = class {
34868
35178
  * create per run + aggregate). DB is left untouched.
34869
35179
  */
34870
35180
  backfill(projectName, opts, onProgress) {
34871
- const project = this.db.select().from(projects).where(eq37(projects.name, projectName)).get();
35181
+ const project = this.db.select().from(projects).where(eq38(projects.name, projectName)).get();
34872
35182
  if (!project) {
34873
35183
  throw new Error(`Project "${projectName}" not found`);
34874
35184
  }
@@ -34881,13 +35191,13 @@ var IntelligenceService = class {
34881
35191
  sinceTimestamp = parsed;
34882
35192
  }
34883
35193
  const allRuns = this.db.select().from(runs).where(
34884
- and27(
34885
- eq37(runs.projectId, project.id),
34886
- or5(eq37(runs.status, "completed"), eq37(runs.status, "partial")),
35194
+ and28(
35195
+ eq38(runs.projectId, project.id),
35196
+ or5(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
34887
35197
  // Backfill must not replay probe runs as if they were real sweeps.
34888
35198
  ne5(runs.trigger, RunTriggers.probe)
34889
35199
  )
34890
- ).orderBy(asc5(runs.finishedAt)).all();
35200
+ ).orderBy(asc6(runs.finishedAt)).all();
34891
35201
  let startIdx = 0;
34892
35202
  let endIdx = allRuns.length;
34893
35203
  if (opts?.fromRunId) {
@@ -34962,7 +35272,7 @@ var IntelligenceService = class {
34962
35272
  return { processed, skipped, totalInsights };
34963
35273
  }
34964
35274
  loadTrackedCompetitors(projectId) {
34965
- return this.db.select({ domain: competitors.domain }).from(competitors).where(eq37(competitors.projectId, projectId)).all().map((r) => r.domain);
35275
+ return this.db.select({ domain: competitors.domain }).from(competitors).where(eq38(competitors.projectId, projectId)).all().map((r) => r.domain);
34966
35276
  }
34967
35277
  /**
34968
35278
  * Wipe transition signals from an analysis result while keeping health.
@@ -34983,15 +35293,15 @@ var IntelligenceService = class {
34983
35293
  }
34984
35294
  persistResult(result, runId, projectId) {
34985
35295
  const previouslyDismissed = /* @__PURE__ */ new Set();
34986
- const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq37(insights.runId, runId)).all();
35296
+ const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq38(insights.runId, runId)).all();
34987
35297
  for (const row of existingInsights) {
34988
35298
  if (row.dismissed) {
34989
35299
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
34990
35300
  }
34991
35301
  }
34992
35302
  this.db.transaction((tx) => {
34993
- tx.delete(insights).where(eq37(insights.runId, runId)).run();
34994
- tx.delete(healthSnapshots).where(eq37(healthSnapshots.runId, runId)).run();
35303
+ tx.delete(insights).where(eq38(insights.runId, runId)).run();
35304
+ tx.delete(healthSnapshots).where(eq38(healthSnapshots.runId, runId)).run();
34995
35305
  const now = (/* @__PURE__ */ new Date()).toISOString();
34996
35306
  for (const insight of result.insights) {
34997
35307
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -35044,24 +35354,24 @@ var IntelligenceService = class {
35044
35354
  applySeverityTiering(rawInsights, excludeRunId, projectId) {
35045
35355
  const regressions = rawInsights.filter((i) => i.type === "regression");
35046
35356
  if (regressions.length === 0) return rawInsights;
35047
- const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq37(gscSearchData.projectId, projectId)).all();
35357
+ const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq38(gscSearchData.projectId, projectId)).all();
35048
35358
  const gscConnected = gscRows.length > 0;
35049
35359
  const gscImpressionsByQuery = /* @__PURE__ */ new Map();
35050
35360
  for (const row of gscRows) {
35051
35361
  const key = row.query.toLowerCase();
35052
35362
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
35053
35363
  }
35054
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq37(projects.id, projectId)).get();
35364
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq38(projects.id, projectId)).get();
35055
35365
  const locationCount = Math.max(
35056
35366
  1,
35057
35367
  (projectRow?.locations ?? []).length
35058
35368
  );
35059
35369
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
35060
35370
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
35061
- and27(
35062
- eq37(runs.projectId, projectId),
35063
- eq37(runs.kind, RunKinds["answer-visibility"]),
35064
- or5(eq37(runs.status, "completed"), eq37(runs.status, "partial")),
35371
+ and28(
35372
+ eq38(runs.projectId, projectId),
35373
+ eq38(runs.kind, RunKinds["answer-visibility"]),
35374
+ or5(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35065
35375
  // Defensive — see top of file.
35066
35376
  ne5(runs.trigger, RunTriggers.probe)
35067
35377
  )
@@ -35081,7 +35391,7 @@ var IntelligenceService = class {
35081
35391
  const haveHistory = recentRunIds.length > 0;
35082
35392
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
35083
35393
  if (haveHistory) {
35084
- const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and27(eq37(insights.type, "regression"), inArray14(insights.runId, recentRunIds))).all();
35394
+ const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and28(eq38(insights.type, "regression"), inArray14(insights.runId, recentRunIds))).all();
35085
35395
  const regressionGroups = /* @__PURE__ */ new Map();
35086
35396
  for (const row of priorRows) {
35087
35397
  if (!row.runId) continue;
@@ -35110,7 +35420,7 @@ var IntelligenceService = class {
35110
35420
  });
35111
35421
  }
35112
35422
  buildRunData(runId, projectId, completedAt, location = null) {
35113
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq37(projects.id, projectId)).get();
35423
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq38(projects.id, projectId)).get();
35114
35424
  const projectDomains = projectDomainRow ? effectiveDomains({
35115
35425
  canonicalDomain: projectDomainRow.canonicalDomain,
35116
35426
  ownedDomains: projectDomainRow.ownedDomains
@@ -35127,7 +35437,7 @@ var IntelligenceService = class {
35127
35437
  citedDomains: querySnapshots.citedDomains,
35128
35438
  competitorOverlap: querySnapshots.competitorOverlap,
35129
35439
  snapshotLocation: querySnapshots.location
35130
- }).from(querySnapshots).leftJoin(queries, eq37(querySnapshots.queryId, queries.id)).where(eq37(querySnapshots.runId, runId)).all();
35440
+ }).from(querySnapshots).leftJoin(queries, eq38(querySnapshots.queryId, queries.id)).where(eq38(querySnapshots.runId, runId)).all();
35131
35441
  const snapshots = [];
35132
35442
  let orphanCount = 0;
35133
35443
  for (const r of rows) {
@@ -35180,6 +35490,7 @@ export {
35180
35490
  schedules,
35181
35491
  notifications,
35182
35492
  gscSearchData,
35493
+ gscDailyTotals,
35183
35494
  gscUrlInspections,
35184
35495
  gscCoverageSnapshots,
35185
35496
  siteAuditSnapshots,
@@ -35195,6 +35506,8 @@ export {
35195
35506
  backlinkDomains,
35196
35507
  backlinkSummaries,
35197
35508
  agentMemory,
35509
+ llmUsageEvents,
35510
+ agentToolEvents,
35198
35511
  crawlerEventsHourly,
35199
35512
  aiUserFetchEventsHourly,
35200
35513
  rawEventSamples,