@ainyc/canonry 4.77.0 → 4.80.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/assets/agent-workspace/skills/canonry/references/canonry-cli.md +18 -0
  2. package/assets/assets/{BacklinksPage-CwXveumn.js → BacklinksPage-dRc62jAY.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-DntKGI5J.js → ChartPrimitives-D2_IvTkk.js} +1 -1
  4. package/assets/assets/{ProjectPage-CVudiU8X.js → ProjectPage-DSuvRUIf.js} +1 -1
  5. package/assets/assets/{RunRow-DMtYXaxG.js → RunRow-C0MA3yuQ.js} +1 -1
  6. package/assets/assets/{RunsPage-Cz-YlucO.js → RunsPage-4uxTYgGy.js} +1 -1
  7. package/assets/assets/{SettingsPage-BCuG3C-0.js → SettingsPage-3-SLhcJ7.js} +1 -1
  8. package/assets/assets/{TrafficPage-DV8X47wa.js → TrafficPage-DZ50qwme.js} +1 -1
  9. package/assets/assets/{TrafficSourceDetailPage-BmYhK9jm.js → TrafficSourceDetailPage-CzK5TMFp.js} +1 -1
  10. package/assets/assets/{arrow-left-CUmHyNnF.js → arrow-left-BaZIkAXX.js} +1 -1
  11. package/assets/assets/{extract-error-message-DFjy9_zi.js → extract-error-message-cpvfuFqW.js} +1 -1
  12. package/assets/assets/{index-D9smxU6R.js → index-EnY_OBRd.js} +70 -70
  13. package/assets/assets/{trash-2-B_UtEEm8.js → trash-2-JpcztiS5.js} +1 -1
  14. package/assets/index.html +1 -1
  15. package/dist/{chunk-XI6YSTGE.js → chunk-2QBSRHSN.js} +187 -1
  16. package/dist/{chunk-KPN22EWK.js → chunk-AVN6Q6LM.js} +138 -2
  17. package/dist/{chunk-BPZWX7YI.js → chunk-CXIGHPBE.js} +1006 -324
  18. package/dist/{chunk-FB43IMZT.js → chunk-LCABGFYN.js} +724 -286
  19. package/dist/cli.js +372 -148
  20. package/dist/index.d.ts +17 -0
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-C76ZRMF5.js → intelligence-service-ZWW3I3NL.js} +2 -2
  23. package/dist/mcp.js +9 -3
  24. package/package.json +11 -10
@@ -34,6 +34,16 @@ import {
34
34
  __export,
35
35
  absolutizeProjectUrl,
36
36
  actionConfidenceLabel,
37
+ adsCampaignListResponseSchema,
38
+ adsConnectRequestSchema,
39
+ adsConnectionStatusDtoSchema,
40
+ adsCpcMicros,
41
+ adsCtr,
42
+ adsDisconnectResponseSchema,
43
+ adsInsightLevelSchema,
44
+ adsInsightsResponseSchema,
45
+ adsSummaryDtoSchema,
46
+ adsSyncResponseSchema,
37
47
  agentProvidersResponseDtoSchema,
38
48
  apiKeyDtoSchema,
39
49
  apiKeyListDtoSchema,
@@ -232,10 +242,10 @@ import {
232
242
  wordpressSchemaDeployResultDtoSchema,
233
243
  wordpressSchemaStatusResultDtoSchema,
234
244
  wordpressStatusDtoSchema
235
- } from "./chunk-KPN22EWK.js";
245
+ } from "./chunk-AVN6Q6LM.js";
236
246
 
237
247
  // src/intelligence-service.ts
238
- import { eq as eq33, desc as desc17, asc as asc4, and as and24, ne as ne5, or as or5, inArray as inArray12, gte as gte6, lte as lte3 } from "drizzle-orm";
248
+ import { eq as eq35, desc as desc17, asc as asc5, and as and25, ne as ne5, or as or5, inArray as inArray13, gte as gte7, lte as lte4 } from "drizzle-orm";
239
249
 
240
250
  // ../db/src/client.ts
241
251
  import { mkdirSync } from "fs";
@@ -246,6 +256,11 @@ import { drizzle } from "drizzle-orm/better-sqlite3";
246
256
  // ../db/src/schema.ts
247
257
  var schema_exports = {};
248
258
  __export(schema_exports, {
259
+ adsAdGroups: () => adsAdGroups,
260
+ adsAds: () => adsAds,
261
+ adsCampaigns: () => adsCampaigns,
262
+ adsConnections: () => adsConnections,
263
+ adsInsightsDaily: () => adsInsightsDaily,
249
264
  agentMemory: () => agentMemory,
250
265
  agentSessions: () => agentSessions,
251
266
  aiReferralEventsHourly: () => aiReferralEventsHourly,
@@ -1219,6 +1234,83 @@ var gbpPlaceDetails = sqliteTable("gbp_place_details", {
1219
1234
  }, (table) => [
1220
1235
  index("idx_gbp_place_details_loc").on(table.projectId, table.locationName, table.syncedAt)
1221
1236
  ]);
1237
+ var adsConnections = sqliteTable("ads_connections", {
1238
+ id: text("id").primaryKey(),
1239
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
1240
+ adAccountId: text("ad_account_id").notNull(),
1241
+ displayName: text("display_name"),
1242
+ currencyCode: text("currency_code"),
1243
+ timezone: text("timezone"),
1244
+ status: text("status"),
1245
+ lastSyncedAt: text("last_synced_at"),
1246
+ createdAt: text("created_at").notNull(),
1247
+ updatedAt: text("updated_at").notNull()
1248
+ }, (table) => [
1249
+ uniqueIndex("idx_ads_conn_project").on(table.projectId)
1250
+ ]);
1251
+ var adsCampaigns = sqliteTable("ads_campaigns", {
1252
+ id: text("id").primaryKey(),
1253
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
1254
+ name: text("name").notNull(),
1255
+ status: text("status").notNull(),
1256
+ biddingType: text("bidding_type"),
1257
+ dailySpendLimitMicros: integer("daily_spend_limit_micros"),
1258
+ lifetimeSpendLimitMicros: integer("lifetime_spend_limit_micros"),
1259
+ targeting: text("targeting", { mode: "json" }).$type(),
1260
+ upstreamCreatedAt: integer("upstream_created_at"),
1261
+ upstreamUpdatedAt: integer("upstream_updated_at"),
1262
+ syncRunId: text("sync_run_id").references(() => runs.id, { onDelete: "set null" }),
1263
+ syncedAt: text("synced_at").notNull()
1264
+ }, (table) => [
1265
+ index("idx_ads_campaigns_project").on(table.projectId)
1266
+ ]);
1267
+ var adsAdGroups = sqliteTable("ads_ad_groups", {
1268
+ id: text("id").primaryKey(),
1269
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
1270
+ campaignId: text("campaign_id").notNull().references(() => adsCampaigns.id, { onDelete: "cascade" }),
1271
+ name: text("name").notNull(),
1272
+ status: text("status").notNull(),
1273
+ billingEventType: text("billing_event_type"),
1274
+ maxBidMicros: integer("max_bid_micros"),
1275
+ contextHints: text("context_hints", { mode: "json" }).$type().notNull().default([]),
1276
+ upstreamCreatedAt: integer("upstream_created_at"),
1277
+ upstreamUpdatedAt: integer("upstream_updated_at"),
1278
+ syncRunId: text("sync_run_id").references(() => runs.id, { onDelete: "set null" }),
1279
+ syncedAt: text("synced_at").notNull()
1280
+ }, (table) => [
1281
+ index("idx_ads_ad_groups_project").on(table.projectId),
1282
+ index("idx_ads_ad_groups_campaign").on(table.campaignId)
1283
+ ]);
1284
+ var adsAds = sqliteTable("ads_ads", {
1285
+ id: text("id").primaryKey(),
1286
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
1287
+ adGroupId: text("ad_group_id").notNull().references(() => adsAdGroups.id, { onDelete: "cascade" }),
1288
+ name: text("name").notNull(),
1289
+ status: text("status").notNull(),
1290
+ creative: text("creative", { mode: "json" }).$type(),
1291
+ reviewStatus: text("review_status"),
1292
+ upstreamCreatedAt: integer("upstream_created_at"),
1293
+ upstreamUpdatedAt: integer("upstream_updated_at"),
1294
+ syncRunId: text("sync_run_id").references(() => runs.id, { onDelete: "set null" }),
1295
+ syncedAt: text("synced_at").notNull()
1296
+ }, (table) => [
1297
+ index("idx_ads_ads_project").on(table.projectId),
1298
+ index("idx_ads_ads_group").on(table.adGroupId)
1299
+ ]);
1300
+ var adsInsightsDaily = sqliteTable("ads_insights_daily", {
1301
+ id: text("id").primaryKey(),
1302
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
1303
+ level: text("level").notNull(),
1304
+ entityId: text("entity_id").notNull(),
1305
+ date: text("date").notNull(),
1306
+ impressions: integer("impressions").notNull().default(0),
1307
+ clicks: integer("clicks").notNull().default(0),
1308
+ spendMicros: integer("spend_micros").notNull().default(0),
1309
+ syncRunId: text("sync_run_id").references(() => runs.id, { onDelete: "set null" })
1310
+ }, (table) => [
1311
+ uniqueIndex("uniq_ads_insights_daily").on(table.projectId, table.level, table.entityId, table.date),
1312
+ index("idx_ads_insights_project_date").on(table.projectId, table.date)
1313
+ ]);
1222
1314
 
1223
1315
  // ../db/src/client.ts
1224
1316
  function createClient(databasePath) {
@@ -2876,6 +2968,89 @@ var MIGRATION_VERSIONS = [
2876
2968
  statements: [
2877
2969
  `ALTER TABLE discovery_sessions ADD COLUMN warning TEXT`
2878
2970
  ]
2971
+ },
2972
+ {
2973
+ // OpenAI Advertiser API (ChatGPT ads) — connection metadata, entity
2974
+ // snapshots (campaigns / ad groups / ads), and daily paid-performance
2975
+ // rollups. One connection per project (ad accounts are not domain-bound).
2976
+ // Money columns are integer micros; ads-sync normalizes the insights
2977
+ // API's decimal-dollar spend at ingest. Credentials live in config.yaml.
2978
+ version: 77,
2979
+ name: "openai-ads-tables",
2980
+ statements: [
2981
+ `CREATE TABLE IF NOT EXISTS ads_connections (
2982
+ id TEXT PRIMARY KEY,
2983
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
2984
+ ad_account_id TEXT NOT NULL,
2985
+ display_name TEXT,
2986
+ currency_code TEXT,
2987
+ timezone TEXT,
2988
+ status TEXT,
2989
+ last_synced_at TEXT,
2990
+ created_at TEXT NOT NULL,
2991
+ updated_at TEXT NOT NULL
2992
+ )`,
2993
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_ads_conn_project ON ads_connections(project_id)`,
2994
+ `CREATE TABLE IF NOT EXISTS ads_campaigns (
2995
+ id TEXT PRIMARY KEY,
2996
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
2997
+ name TEXT NOT NULL,
2998
+ status TEXT NOT NULL,
2999
+ bidding_type TEXT,
3000
+ daily_spend_limit_micros INTEGER,
3001
+ lifetime_spend_limit_micros INTEGER,
3002
+ targeting TEXT,
3003
+ upstream_created_at INTEGER,
3004
+ upstream_updated_at INTEGER,
3005
+ sync_run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
3006
+ synced_at TEXT NOT NULL
3007
+ )`,
3008
+ `CREATE INDEX IF NOT EXISTS idx_ads_campaigns_project ON ads_campaigns(project_id)`,
3009
+ `CREATE TABLE IF NOT EXISTS ads_ad_groups (
3010
+ id TEXT PRIMARY KEY,
3011
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
3012
+ campaign_id TEXT NOT NULL REFERENCES ads_campaigns(id) ON DELETE CASCADE,
3013
+ name TEXT NOT NULL,
3014
+ status TEXT NOT NULL,
3015
+ billing_event_type TEXT,
3016
+ max_bid_micros INTEGER,
3017
+ context_hints TEXT NOT NULL DEFAULT '[]',
3018
+ upstream_created_at INTEGER,
3019
+ upstream_updated_at INTEGER,
3020
+ sync_run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
3021
+ synced_at TEXT NOT NULL
3022
+ )`,
3023
+ `CREATE INDEX IF NOT EXISTS idx_ads_ad_groups_project ON ads_ad_groups(project_id)`,
3024
+ `CREATE INDEX IF NOT EXISTS idx_ads_ad_groups_campaign ON ads_ad_groups(campaign_id)`,
3025
+ `CREATE TABLE IF NOT EXISTS ads_ads (
3026
+ id TEXT PRIMARY KEY,
3027
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
3028
+ ad_group_id TEXT NOT NULL REFERENCES ads_ad_groups(id) ON DELETE CASCADE,
3029
+ name TEXT NOT NULL,
3030
+ status TEXT NOT NULL,
3031
+ creative TEXT,
3032
+ review_status TEXT,
3033
+ upstream_created_at INTEGER,
3034
+ upstream_updated_at INTEGER,
3035
+ sync_run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
3036
+ synced_at TEXT NOT NULL
3037
+ )`,
3038
+ `CREATE INDEX IF NOT EXISTS idx_ads_ads_project ON ads_ads(project_id)`,
3039
+ `CREATE INDEX IF NOT EXISTS idx_ads_ads_group ON ads_ads(ad_group_id)`,
3040
+ `CREATE TABLE IF NOT EXISTS ads_insights_daily (
3041
+ id TEXT PRIMARY KEY,
3042
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
3043
+ level TEXT NOT NULL,
3044
+ entity_id TEXT NOT NULL,
3045
+ date TEXT NOT NULL,
3046
+ impressions INTEGER NOT NULL DEFAULT 0,
3047
+ clicks INTEGER NOT NULL DEFAULT 0,
3048
+ spend_micros INTEGER NOT NULL DEFAULT 0,
3049
+ sync_run_id TEXT REFERENCES runs(id) ON DELETE SET NULL
3050
+ )`,
3051
+ `CREATE UNIQUE INDEX IF NOT EXISTS uniq_ads_insights_daily ON ads_insights_daily(project_id, level, entity_id, date)`,
3052
+ `CREATE INDEX IF NOT EXISTS idx_ads_insights_project_date ON ads_insights_daily(project_id, date)`
3053
+ ]
2879
3054
  }
2880
3055
  ];
2881
3056
  function isDuplicateColumnError(err) {
@@ -13020,6 +13195,12 @@ function makeSnippet(text2, query) {
13020
13195
  import { z } from "zod";
13021
13196
  var SCHEMA_TABLE = {
13022
13197
  AgentProvidersResponseDto: agentProvidersResponseDtoSchema,
13198
+ AdsCampaignListResponse: adsCampaignListResponseSchema,
13199
+ AdsConnectionStatusDto: adsConnectionStatusDtoSchema,
13200
+ AdsDisconnectResponse: adsDisconnectResponseSchema,
13201
+ AdsInsightsResponse: adsInsightsResponseSchema,
13202
+ AdsSummaryDto: adsSummaryDtoSchema,
13203
+ AdsSyncResponse: adsSyncResponseSchema,
13023
13204
  ApiKeyDto: apiKeyDtoSchema,
13024
13205
  ApiKeyListDto: apiKeyListDtoSchema,
13025
13206
  AuditLogEntry: auditLogEntrySchema,
@@ -15061,6 +15242,106 @@ var routeCatalog = [
15061
15242
  404: errorResponse("Project not found.")
15062
15243
  }
15063
15244
  },
15245
+ {
15246
+ method: "post",
15247
+ path: "/api/v1/projects/{name}/ads/connect",
15248
+ summary: "Connect an OpenAI ad account (ChatGPT ads) with an Ads Manager SDK key",
15249
+ tags: ["ads"],
15250
+ parameters: [nameParameter],
15251
+ requestBody: {
15252
+ required: true,
15253
+ content: {
15254
+ "application/json": {
15255
+ schema: {
15256
+ type: "object",
15257
+ required: ["apiKey"],
15258
+ properties: {
15259
+ apiKey: stringSchema
15260
+ }
15261
+ }
15262
+ }
15263
+ }
15264
+ },
15265
+ responses: {
15266
+ 200: jsonResponse("Connected; key validated against the upstream ad account.", "AdsConnectionStatusDto"),
15267
+ 400: errorResponse("Missing/invalid key or credential storage unavailable."),
15268
+ 404: errorResponse("Project not found.")
15269
+ }
15270
+ },
15271
+ {
15272
+ method: "delete",
15273
+ path: "/api/v1/projects/{name}/ads/connection",
15274
+ summary: "Disconnect the OpenAI ad account (removes the stored credential)",
15275
+ tags: ["ads"],
15276
+ parameters: [nameParameter],
15277
+ responses: {
15278
+ 200: jsonResponse("Disconnected (idempotent).", "AdsDisconnectResponse"),
15279
+ 404: errorResponse("Project not found.")
15280
+ }
15281
+ },
15282
+ {
15283
+ method: "get",
15284
+ path: "/api/v1/projects/{name}/ads/status",
15285
+ summary: "OpenAI ads connection status and last sync time",
15286
+ tags: ["ads"],
15287
+ parameters: [nameParameter],
15288
+ responses: {
15289
+ 200: jsonResponse("Connection status.", "AdsConnectionStatusDto"),
15290
+ 404: errorResponse("Project not found.")
15291
+ }
15292
+ },
15293
+ {
15294
+ method: "post",
15295
+ path: "/api/v1/projects/{name}/ads/sync",
15296
+ summary: "Trigger an ads-sync run (entity snapshots + daily paid-performance rollups)",
15297
+ tags: ["ads"],
15298
+ parameters: [nameParameter],
15299
+ responses: {
15300
+ 200: jsonResponse("Sync run queued.", "AdsSyncResponse"),
15301
+ 400: errorResponse("No ads connection for this project."),
15302
+ 404: errorResponse("Project not found.")
15303
+ }
15304
+ },
15305
+ {
15306
+ method: "get",
15307
+ path: "/api/v1/projects/{name}/ads/campaigns",
15308
+ summary: "Synced campaign snapshots with nested ad groups (context hints) and ads",
15309
+ tags: ["ads"],
15310
+ parameters: [nameParameter],
15311
+ responses: {
15312
+ 200: jsonResponse("Campaign snapshots.", "AdsCampaignListResponse"),
15313
+ 404: errorResponse("Project not found.")
15314
+ }
15315
+ },
15316
+ {
15317
+ method: "get",
15318
+ path: "/api/v1/projects/{name}/ads/insights",
15319
+ summary: "Daily paid-performance rollups (spend in integer micros; ctr/cpc derived server-side)",
15320
+ tags: ["ads"],
15321
+ parameters: [
15322
+ nameParameter,
15323
+ { in: "query", name: "level", required: false, description: "campaign | ad_group", schema: stringSchema },
15324
+ { in: "query", name: "entityId", required: false, description: "Scope to one upstream entity id", schema: stringSchema },
15325
+ { in: "query", name: "from", required: false, description: "Inclusive start date (YYYY-MM-DD)", schema: stringSchema },
15326
+ { in: "query", name: "to", required: false, description: "Inclusive end date (YYYY-MM-DD)", schema: stringSchema }
15327
+ ],
15328
+ responses: {
15329
+ 200: jsonResponse("Daily rollup rows.", "AdsInsightsResponse"),
15330
+ 400: errorResponse("Invalid level filter."),
15331
+ 404: errorResponse("Project not found.")
15332
+ }
15333
+ },
15334
+ {
15335
+ method: "get",
15336
+ path: "/api/v1/projects/{name}/ads/summary",
15337
+ summary: "Composite paid-performance summary (campaign-level totals; all derived metrics)",
15338
+ tags: ["ads"],
15339
+ parameters: [nameParameter],
15340
+ responses: {
15341
+ 200: jsonResponse("Summary returned.", "AdsSummaryDto"),
15342
+ 404: errorResponse("Project not found.")
15343
+ }
15344
+ },
15064
15345
  {
15065
15346
  method: "post",
15066
15347
  path: "/api/v1/projects/{name}/bing/connect",
@@ -20351,9 +20632,269 @@ async function googleRoutes(app, opts) {
20351
20632
  });
20352
20633
  }
20353
20634
 
20354
- // ../api-routes/src/bing.ts
20635
+ // ../api-routes/src/ads.ts
20355
20636
  import crypto18 from "crypto";
20356
- import { eq as eq21, and as and14, desc as desc11 } from "drizzle-orm";
20637
+ import { eq as eq21, and as and14, asc as asc2, gte as gte3, lte as lte2, inArray as inArray10 } from "drizzle-orm";
20638
+ function statusDto(row) {
20639
+ if (!row) return { connected: false };
20640
+ return {
20641
+ connected: true,
20642
+ adAccountId: row.adAccountId,
20643
+ displayName: row.displayName,
20644
+ currencyCode: row.currencyCode,
20645
+ timezone: row.timezone,
20646
+ status: row.status,
20647
+ lastSyncedAt: row.lastSyncedAt
20648
+ };
20649
+ }
20650
+ function creativeDto(raw) {
20651
+ if (raw == null || typeof raw !== "object") return null;
20652
+ const c = raw;
20653
+ return {
20654
+ type: typeof c.type === "string" ? c.type : null,
20655
+ title: typeof c.title === "string" ? c.title : null,
20656
+ body: typeof c.body === "string" ? c.body : null,
20657
+ targetUrl: typeof c.target_url === "string" ? c.target_url : null
20658
+ };
20659
+ }
20660
+ async function adsRoutes(app, opts) {
20661
+ app.post(
20662
+ "/projects/:name/ads/connect",
20663
+ async (request) => {
20664
+ const project = resolveProject(app.db, request.params.name);
20665
+ const parsed = adsConnectRequestSchema.safeParse(request.body ?? {});
20666
+ if (!parsed.success) throw validationError('"apiKey" is required');
20667
+ if (!opts.adsCredentialStore || !opts.verifyAdsAccount) {
20668
+ throw validationError("Ads credential storage is not configured for this deployment");
20669
+ }
20670
+ let account;
20671
+ try {
20672
+ account = await opts.verifyAdsAccount(parsed.data.apiKey);
20673
+ } catch (err) {
20674
+ const message = err instanceof Error ? err.message : String(err);
20675
+ throw validationError(`OpenAI Ads API rejected the key: ${message}`);
20676
+ }
20677
+ const now = (/* @__PURE__ */ new Date()).toISOString();
20678
+ const existingCfg = opts.adsCredentialStore.getConnection(project.name);
20679
+ opts.adsCredentialStore.upsertConnection({
20680
+ projectName: project.name,
20681
+ apiKey: parsed.data.apiKey,
20682
+ adAccountId: account.id,
20683
+ createdAt: existingCfg?.createdAt ?? now,
20684
+ updatedAt: now
20685
+ });
20686
+ const existingRow = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
20687
+ app.db.transaction((tx) => {
20688
+ if (existingRow) {
20689
+ tx.update(adsConnections).set({
20690
+ adAccountId: account.id,
20691
+ displayName: account.name,
20692
+ currencyCode: account.currencyCode,
20693
+ timezone: account.timezone,
20694
+ status: account.status,
20695
+ updatedAt: now
20696
+ }).where(eq21(adsConnections.id, existingRow.id)).run();
20697
+ } else {
20698
+ tx.insert(adsConnections).values({
20699
+ id: crypto18.randomUUID(),
20700
+ projectId: project.id,
20701
+ adAccountId: account.id,
20702
+ displayName: account.name,
20703
+ currencyCode: account.currencyCode,
20704
+ timezone: account.timezone,
20705
+ status: account.status,
20706
+ createdAt: now,
20707
+ updatedAt: now
20708
+ }).run();
20709
+ }
20710
+ writeAuditLog(tx, auditFromRequest(request, {
20711
+ projectId: project.id,
20712
+ actor: "api",
20713
+ action: "ads.connected",
20714
+ entityType: "ads-connection",
20715
+ entityId: account.id
20716
+ }));
20717
+ });
20718
+ const row = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
20719
+ return statusDto(row);
20720
+ }
20721
+ );
20722
+ app.delete("/projects/:name/ads/connection", async (request) => {
20723
+ const project = resolveProject(app.db, request.params.name);
20724
+ const row = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
20725
+ if (row) {
20726
+ app.db.transaction((tx) => {
20727
+ tx.delete(adsConnections).where(eq21(adsConnections.id, row.id)).run();
20728
+ writeAuditLog(tx, auditFromRequest(request, {
20729
+ projectId: project.id,
20730
+ actor: "api",
20731
+ action: "ads.disconnected",
20732
+ entityType: "ads-connection",
20733
+ entityId: row.adAccountId
20734
+ }));
20735
+ });
20736
+ }
20737
+ const removedFromConfig = opts.adsCredentialStore?.removeConnection(project.name) ?? false;
20738
+ const response = { disconnected: Boolean(row) || removedFromConfig };
20739
+ return response;
20740
+ });
20741
+ app.get("/projects/:name/ads/status", async (request) => {
20742
+ const project = resolveProject(app.db, request.params.name);
20743
+ const row = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
20744
+ return statusDto(row);
20745
+ });
20746
+ app.post("/projects/:name/ads/sync", async (request) => {
20747
+ const project = resolveProject(app.db, request.params.name);
20748
+ const row = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
20749
+ if (!row) {
20750
+ throw validationError('No ads connection for this project. Run "canonry ads connect" first.');
20751
+ }
20752
+ const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and14(
20753
+ eq21(runs.projectId, project.id),
20754
+ eq21(runs.kind, RunKinds["ads-sync"]),
20755
+ inArray10(runs.status, [RunStatuses.queued, RunStatuses.running])
20756
+ )).get();
20757
+ if (inFlight) {
20758
+ const existing = { runId: inFlight.id, status: inFlight.status };
20759
+ return existing;
20760
+ }
20761
+ const runId = crypto18.randomUUID();
20762
+ app.db.insert(runs).values({
20763
+ id: runId,
20764
+ projectId: project.id,
20765
+ kind: RunKinds["ads-sync"],
20766
+ status: RunStatuses.queued,
20767
+ trigger: RunTriggers.manual,
20768
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
20769
+ }).run();
20770
+ opts.onAdsSyncRequested?.(runId, project.id);
20771
+ const response = { runId, status: RunStatuses.queued };
20772
+ return response;
20773
+ });
20774
+ app.get("/projects/:name/ads/campaigns", async (request) => {
20775
+ const project = resolveProject(app.db, request.params.name);
20776
+ const campaignRows = app.db.select().from(adsCampaigns).where(eq21(adsCampaigns.projectId, project.id)).all();
20777
+ const groupRows = app.db.select().from(adsAdGroups).where(eq21(adsAdGroups.projectId, project.id)).all();
20778
+ const adRows = app.db.select().from(adsAds).where(eq21(adsAds.projectId, project.id)).all();
20779
+ const adsByGroup = /* @__PURE__ */ new Map();
20780
+ for (const ad of adRows) {
20781
+ const dto = {
20782
+ id: ad.id,
20783
+ adGroupId: ad.adGroupId,
20784
+ name: ad.name,
20785
+ status: ad.status,
20786
+ reviewStatus: ad.reviewStatus,
20787
+ creative: creativeDto(ad.creative)
20788
+ };
20789
+ const list = adsByGroup.get(ad.adGroupId) ?? [];
20790
+ list.push(dto);
20791
+ adsByGroup.set(ad.adGroupId, list);
20792
+ }
20793
+ const groupsByCampaign = /* @__PURE__ */ new Map();
20794
+ for (const group of groupRows) {
20795
+ const dto = {
20796
+ id: group.id,
20797
+ campaignId: group.campaignId,
20798
+ name: group.name,
20799
+ status: group.status,
20800
+ billingEventType: group.billingEventType,
20801
+ maxBidMicros: group.maxBidMicros,
20802
+ contextHints: group.contextHints,
20803
+ ads: adsByGroup.get(group.id) ?? []
20804
+ };
20805
+ const list = groupsByCampaign.get(group.campaignId) ?? [];
20806
+ list.push(dto);
20807
+ groupsByCampaign.set(group.campaignId, list);
20808
+ }
20809
+ const campaigns = campaignRows.map((campaign) => ({
20810
+ id: campaign.id,
20811
+ name: campaign.name,
20812
+ status: campaign.status,
20813
+ biddingType: campaign.biddingType,
20814
+ dailySpendLimitMicros: campaign.dailySpendLimitMicros,
20815
+ lifetimeSpendLimitMicros: campaign.lifetimeSpendLimitMicros,
20816
+ adGroups: groupsByCampaign.get(campaign.id) ?? []
20817
+ }));
20818
+ const response = { campaigns };
20819
+ return response;
20820
+ });
20821
+ app.get("/projects/:name/ads/insights", async (request) => {
20822
+ const project = resolveProject(app.db, request.params.name);
20823
+ const { level, entityId, from, to } = request.query;
20824
+ let parsedLevel;
20825
+ if (level !== void 0) {
20826
+ const result = adsInsightLevelSchema.safeParse(level);
20827
+ if (!result.success) {
20828
+ throw validationError('"level" must be one of: campaign, ad_group');
20829
+ }
20830
+ parsedLevel = result.data;
20831
+ }
20832
+ const conditions = [eq21(adsInsightsDaily.projectId, project.id)];
20833
+ if (parsedLevel) conditions.push(eq21(adsInsightsDaily.level, parsedLevel));
20834
+ if (entityId) conditions.push(eq21(adsInsightsDaily.entityId, entityId));
20835
+ if (from) conditions.push(gte3(adsInsightsDaily.date, from));
20836
+ if (to) conditions.push(lte2(adsInsightsDaily.date, to));
20837
+ const rows = app.db.select().from(adsInsightsDaily).where(and14(...conditions)).orderBy(asc2(adsInsightsDaily.date)).all();
20838
+ const dtoRows = rows.map((row) => ({
20839
+ level: row.level,
20840
+ entityId: row.entityId,
20841
+ date: row.date,
20842
+ impressions: row.impressions,
20843
+ clicks: row.clicks,
20844
+ spendMicros: row.spendMicros,
20845
+ ctr: adsCtr(row.clicks, row.impressions),
20846
+ cpcMicros: adsCpcMicros(row.spendMicros, row.clicks)
20847
+ }));
20848
+ const conn = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
20849
+ const response = { rows: dtoRows, currencyCode: conn?.currencyCode ?? null };
20850
+ return response;
20851
+ });
20852
+ app.get("/projects/:name/ads/summary", async (request) => {
20853
+ const project = resolveProject(app.db, request.params.name);
20854
+ const row = app.db.select().from(adsConnections).where(eq21(adsConnections.projectId, project.id)).get();
20855
+ const campaignCount = app.db.select().from(adsCampaigns).where(eq21(adsCampaigns.projectId, project.id)).all().length;
20856
+ const adGroupCount = app.db.select().from(adsAdGroups).where(eq21(adsAdGroups.projectId, project.id)).all().length;
20857
+ const adCount = app.db.select().from(adsAds).where(eq21(adsAds.projectId, project.id)).all().length;
20858
+ const campaignInsights = app.db.select().from(adsInsightsDaily).where(and14(
20859
+ eq21(adsInsightsDaily.projectId, project.id),
20860
+ eq21(adsInsightsDaily.level, "campaign")
20861
+ )).all();
20862
+ let impressions = 0;
20863
+ let clicks = 0;
20864
+ let spendMicros = 0;
20865
+ let fromDate = null;
20866
+ let toDate = null;
20867
+ for (const insight of campaignInsights) {
20868
+ impressions += insight.impressions;
20869
+ clicks += insight.clicks;
20870
+ spendMicros += insight.spendMicros;
20871
+ if (fromDate === null || insight.date < fromDate) fromDate = insight.date;
20872
+ if (toDate === null || insight.date > toDate) toDate = insight.date;
20873
+ }
20874
+ const response = {
20875
+ connected: Boolean(row),
20876
+ displayName: row?.displayName ?? null,
20877
+ currencyCode: row?.currencyCode ?? null,
20878
+ lastSyncedAt: row?.lastSyncedAt ?? null,
20879
+ campaignCount,
20880
+ adGroupCount,
20881
+ adCount,
20882
+ window: { from: fromDate, to: toDate },
20883
+ totals: {
20884
+ impressions,
20885
+ clicks,
20886
+ spendMicros,
20887
+ ctr: adsCtr(clicks, impressions),
20888
+ cpcMicros: adsCpcMicros(spendMicros, clicks)
20889
+ }
20890
+ };
20891
+ return response;
20892
+ });
20893
+ }
20894
+
20895
+ // ../api-routes/src/bing.ts
20896
+ import crypto19 from "crypto";
20897
+ import { eq as eq22, and as and15, desc as desc11 } from "drizzle-orm";
20357
20898
 
20358
20899
  // ../integration-bing/src/constants.ts
20359
20900
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
@@ -20681,7 +21222,7 @@ async function bingRoutes(app, opts) {
20681
21222
  const store = requireConnectionStore();
20682
21223
  const project = resolveProject(app.db, request.params.name);
20683
21224
  requireConnection(store, project.canonicalDomain);
20684
- const allInspections = app.db.select().from(bingUrlInspections).where(eq21(bingUrlInspections.projectId, project.id)).orderBy(desc11(bingUrlInspections.inspectedAt)).all();
21225
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq22(bingUrlInspections.projectId, project.id)).orderBy(desc11(bingUrlInspections.inspectedAt)).all();
20685
21226
  const latestByUrl = /* @__PURE__ */ new Map();
20686
21227
  const definitiveByUrl = /* @__PURE__ */ new Map();
20687
21228
  for (const row of allInspections) {
@@ -20738,7 +21279,7 @@ async function bingRoutes(app, opts) {
20738
21279
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
20739
21280
  const now = (/* @__PURE__ */ new Date()).toISOString();
20740
21281
  app.db.insert(bingCoverageSnapshots).values({
20741
- id: crypto18.randomUUID(),
21282
+ id: crypto19.randomUUID(),
20742
21283
  projectId: project.id,
20743
21284
  syncRunId: snapshotRunId,
20744
21285
  date: snapshotDate,
@@ -20770,7 +21311,7 @@ async function bingRoutes(app, opts) {
20770
21311
  const project = resolveProject(app.db, request.params.name);
20771
21312
  const parsed = parseInt(request.query.limit ?? "90", 10);
20772
21313
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
20773
- const rows = app.db.select().from(bingCoverageSnapshots).where(eq21(bingCoverageSnapshots.projectId, project.id)).orderBy(desc11(bingCoverageSnapshots.date)).limit(limit).all();
21314
+ const rows = app.db.select().from(bingCoverageSnapshots).where(eq22(bingCoverageSnapshots.projectId, project.id)).orderBy(desc11(bingCoverageSnapshots.date)).limit(limit).all();
20774
21315
  return rows.map((r) => ({
20775
21316
  date: r.date,
20776
21317
  indexed: r.indexed,
@@ -20782,7 +21323,7 @@ async function bingRoutes(app, opts) {
20782
21323
  requireConnectionStore();
20783
21324
  const project = resolveProject(app.db, request.params.name);
20784
21325
  const { url, limit } = request.query;
20785
- const whereClause = url ? and14(eq21(bingUrlInspections.projectId, project.id), eq21(bingUrlInspections.url, url)) : eq21(bingUrlInspections.projectId, project.id);
21326
+ const whereClause = url ? and15(eq22(bingUrlInspections.projectId, project.id), eq22(bingUrlInspections.url, url)) : eq22(bingUrlInspections.projectId, project.id);
20786
21327
  const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc11(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
20787
21328
  return filtered.map((r) => ({
20788
21329
  id: r.id,
@@ -20809,7 +21350,7 @@ async function bingRoutes(app, opts) {
20809
21350
  throw validationError("url is required");
20810
21351
  }
20811
21352
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
20812
- const runId = crypto18.randomUUID();
21353
+ const runId = crypto19.randomUUID();
20813
21354
  app.db.insert(runs).values({
20814
21355
  id: runId,
20815
21356
  projectId: project.id,
@@ -20830,7 +21371,7 @@ async function bingRoutes(app, opts) {
20830
21371
  discoveryDate: result.DiscoveryDate ?? null
20831
21372
  });
20832
21373
  const now = (/* @__PURE__ */ new Date()).toISOString();
20833
- const id = crypto18.randomUUID();
21374
+ const id = crypto19.randomUUID();
20834
21375
  const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
20835
21376
  const lastCrawledDate = parseBingDate(result.LastCrawledDate);
20836
21377
  const inIndexDate = parseBingDate(result.InIndexDate);
@@ -20872,7 +21413,7 @@ async function bingRoutes(app, opts) {
20872
21413
  anchorCount: result.AnchorCount ?? null,
20873
21414
  discoveryDate
20874
21415
  }).run();
20875
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq21(runs.id, runId)).run();
21416
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq22(runs.id, runId)).run();
20876
21417
  return {
20877
21418
  id,
20878
21419
  url,
@@ -20888,7 +21429,7 @@ async function bingRoutes(app, opts) {
20888
21429
  } catch (e) {
20889
21430
  const msg = e instanceof Error ? e.message : String(e);
20890
21431
  bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
20891
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq21(runs.id, runId)).run();
21432
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
20892
21433
  throw e;
20893
21434
  }
20894
21435
  });
@@ -20900,7 +21441,7 @@ async function bingRoutes(app, opts) {
20900
21441
  throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
20901
21442
  }
20902
21443
  const now = (/* @__PURE__ */ new Date()).toISOString();
20903
- const runId = crypto18.randomUUID();
21444
+ const runId = crypto19.randomUUID();
20904
21445
  app.db.insert(runs).values({
20905
21446
  id: runId,
20906
21447
  projectId: project.id,
@@ -20915,7 +21456,7 @@ async function bingRoutes(app, opts) {
20915
21456
  } else {
20916
21457
  bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
20917
21458
  }
20918
- const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
21459
+ const run = app.db.select().from(runs).where(eq22(runs.id, runId)).get();
20919
21460
  return run;
20920
21461
  });
20921
21462
  app.post("/projects/:name/bing/request-indexing", async (request) => {
@@ -20927,7 +21468,7 @@ async function bingRoutes(app, opts) {
20927
21468
  }
20928
21469
  let urlsToSubmit = request.body?.urls ?? [];
20929
21470
  if (request.body?.allUnindexed) {
20930
- const allInspections = app.db.select().from(bingUrlInspections).where(eq21(bingUrlInspections.projectId, project.id)).orderBy(desc11(bingUrlInspections.inspectedAt)).all();
21471
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq22(bingUrlInspections.projectId, project.id)).orderBy(desc11(bingUrlInspections.inspectedAt)).all();
20931
21472
  const latestByUrl = /* @__PURE__ */ new Map();
20932
21473
  for (const row of allInspections) {
20933
21474
  if (!latestByUrl.has(row.url)) {
@@ -21014,14 +21555,14 @@ async function bingRoutes(app, opts) {
21014
21555
  import fs from "fs";
21015
21556
  import path from "path";
21016
21557
  import os from "os";
21017
- import { eq as eq22, and as and15 } from "drizzle-orm";
21558
+ import { eq as eq23, and as and16 } from "drizzle-orm";
21018
21559
  function getScreenshotDir() {
21019
21560
  return path.join(os.homedir(), ".canonry", "screenshots");
21020
21561
  }
21021
21562
  async function cdpRoutes(app, opts) {
21022
21563
  app.get("/screenshots/:snapshotId", async (request, reply) => {
21023
21564
  const { snapshotId } = request.params;
21024
- const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(eq22(querySnapshots.id, snapshotId)).get();
21565
+ const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(eq23(querySnapshots.id, snapshotId)).get();
21025
21566
  if (!snapshot?.screenshotPath) {
21026
21567
  const err = notFound("Screenshot", snapshotId);
21027
21568
  return reply.code(err.statusCode).send(err.toJSON());
@@ -21088,7 +21629,7 @@ async function cdpRoutes(app, opts) {
21088
21629
  async (request, reply) => {
21089
21630
  const project = resolveProject(app.db, request.params.name);
21090
21631
  const { runId } = request.params;
21091
- const run = app.db.select().from(runs).where(and15(eq22(runs.id, runId), eq22(runs.projectId, project.id))).get();
21632
+ const run = app.db.select().from(runs).where(and16(eq23(runs.id, runId), eq23(runs.projectId, project.id))).get();
21092
21633
  if (!run) {
21093
21634
  const err = notFound("Run", runId);
21094
21635
  return reply.code(err.statusCode).send(err.toJSON());
@@ -21101,8 +21642,8 @@ async function cdpRoutes(app, opts) {
21101
21642
  citedDomains: querySnapshots.citedDomains,
21102
21643
  screenshotPath: querySnapshots.screenshotPath,
21103
21644
  rawResponse: querySnapshots.rawResponse
21104
- }).from(querySnapshots).where(eq22(querySnapshots.runId, runId)).all());
21105
- const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq22(queries.projectId, project.id)).all();
21645
+ }).from(querySnapshots).where(eq23(querySnapshots.runId, runId)).all());
21646
+ const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq23(queries.projectId, project.id)).all();
21106
21647
  const queryMap = new Map(queryRows.map((q) => [q.id, q.query]));
21107
21648
  const byQuery = /* @__PURE__ */ new Map();
21108
21649
  for (const snap of snapshots) {
@@ -21184,8 +21725,8 @@ async function cdpRoutes(app, opts) {
21184
21725
  }
21185
21726
 
21186
21727
  // ../api-routes/src/ga.ts
21187
- import crypto19 from "crypto";
21188
- import { eq as eq23, desc as desc12, and as and16, sql as sql9 } from "drizzle-orm";
21728
+ import crypto20 from "crypto";
21729
+ import { eq as eq24, desc as desc12, and as and17, sql as sql9 } from "drizzle-orm";
21189
21730
  function gaLog(level, action, ctx) {
21190
21731
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
21191
21732
  const stream = level === "error" ? process.stderr : process.stdout;
@@ -21392,10 +21933,10 @@ async function ga4Routes(app, opts) {
21392
21933
  if (!saConn && !oauthConn) {
21393
21934
  throw notFound("GA4 connection", project.name);
21394
21935
  }
21395
- app.db.delete(gaTrafficSnapshots).where(eq23(gaTrafficSnapshots.projectId, project.id)).run();
21396
- app.db.delete(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).run();
21397
- app.db.delete(gaAiReferrals).where(eq23(gaAiReferrals.projectId, project.id)).run();
21398
- app.db.delete(gaSocialReferrals).where(eq23(gaSocialReferrals.projectId, project.id)).run();
21936
+ app.db.delete(gaTrafficSnapshots).where(eq24(gaTrafficSnapshots.projectId, project.id)).run();
21937
+ app.db.delete(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).run();
21938
+ app.db.delete(gaAiReferrals).where(eq24(gaAiReferrals.projectId, project.id)).run();
21939
+ app.db.delete(gaSocialReferrals).where(eq24(gaSocialReferrals.projectId, project.id)).run();
21399
21940
  const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
21400
21941
  opts.ga4CredentialStore?.deleteConnection(project.name);
21401
21942
  opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
@@ -21416,7 +21957,7 @@ async function ga4Routes(app, opts) {
21416
21957
  if (!connected) {
21417
21958
  return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
21418
21959
  }
21419
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).orderBy(desc12(gaTrafficSummaries.syncedAt)).limit(1).get();
21960
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).orderBy(desc12(gaTrafficSummaries.syncedAt)).limit(1).get();
21420
21961
  return {
21421
21962
  connected: true,
21422
21963
  propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
@@ -21440,7 +21981,7 @@ async function ga4Routes(app, opts) {
21440
21981
  const syncAi = !only || only === "ai";
21441
21982
  const syncSocial = !only || only === "social";
21442
21983
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
21443
- const runId = crypto19.randomUUID();
21984
+ const runId = crypto20.randomUUID();
21444
21985
  app.db.insert(runs).values({
21445
21986
  id: runId,
21446
21987
  projectId: project.id,
@@ -21480,15 +22021,15 @@ async function ga4Routes(app, opts) {
21480
22021
  app.db.transaction((tx) => {
21481
22022
  if (syncTraffic) {
21482
22023
  tx.delete(gaTrafficSnapshots).where(
21483
- and16(
21484
- eq23(gaTrafficSnapshots.projectId, project.id),
22024
+ and17(
22025
+ eq24(gaTrafficSnapshots.projectId, project.id),
21485
22026
  sql9`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
21486
22027
  sql9`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
21487
22028
  )
21488
22029
  ).run();
21489
22030
  for (const row of rows) {
21490
22031
  tx.insert(gaTrafficSnapshots).values({
21491
- id: crypto19.randomUUID(),
22032
+ id: crypto20.randomUUID(),
21492
22033
  projectId: project.id,
21493
22034
  date: row.date,
21494
22035
  landingPage: row.landingPage,
@@ -21504,15 +22045,15 @@ async function ga4Routes(app, opts) {
21504
22045
  }
21505
22046
  if (syncAi) {
21506
22047
  tx.delete(gaAiReferrals).where(
21507
- and16(
21508
- eq23(gaAiReferrals.projectId, project.id),
22048
+ and17(
22049
+ eq24(gaAiReferrals.projectId, project.id),
21509
22050
  sql9`${gaAiReferrals.date} >= ${summary.periodStart}`,
21510
22051
  sql9`${gaAiReferrals.date} <= ${summary.periodEnd}`
21511
22052
  )
21512
22053
  ).run();
21513
22054
  for (const row of aiReferrals) {
21514
22055
  tx.insert(gaAiReferrals).values({
21515
- id: crypto19.randomUUID(),
22056
+ id: crypto20.randomUUID(),
21516
22057
  projectId: project.id,
21517
22058
  date: row.date,
21518
22059
  source: row.source,
@@ -21530,15 +22071,15 @@ async function ga4Routes(app, opts) {
21530
22071
  }
21531
22072
  if (syncSocial) {
21532
22073
  tx.delete(gaSocialReferrals).where(
21533
- and16(
21534
- eq23(gaSocialReferrals.projectId, project.id),
22074
+ and17(
22075
+ eq24(gaSocialReferrals.projectId, project.id),
21535
22076
  sql9`${gaSocialReferrals.date} >= ${summary.periodStart}`,
21536
22077
  sql9`${gaSocialReferrals.date} <= ${summary.periodEnd}`
21537
22078
  )
21538
22079
  ).run();
21539
22080
  for (const row of socialReferrals) {
21540
22081
  tx.insert(gaSocialReferrals).values({
21541
- id: crypto19.randomUUID(),
22082
+ id: crypto20.randomUUID(),
21542
22083
  projectId: project.id,
21543
22084
  date: row.date,
21544
22085
  source: row.source,
@@ -21552,9 +22093,9 @@ async function ga4Routes(app, opts) {
21552
22093
  }
21553
22094
  }
21554
22095
  if (syncSummary) {
21555
- tx.delete(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).run();
22096
+ tx.delete(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).run();
21556
22097
  tx.insert(gaTrafficSummaries).values({
21557
- id: crypto19.randomUUID(),
22098
+ id: crypto20.randomUUID(),
21558
22099
  projectId: project.id,
21559
22100
  periodStart: summary.periodStart,
21560
22101
  periodEnd: summary.periodEnd,
@@ -21564,10 +22105,10 @@ async function ga4Routes(app, opts) {
21564
22105
  syncedAt: now,
21565
22106
  syncRunId: runId
21566
22107
  }).run();
21567
- tx.delete(gaTrafficWindowSummaries).where(eq23(gaTrafficWindowSummaries.projectId, project.id)).run();
22108
+ tx.delete(gaTrafficWindowSummaries).where(eq24(gaTrafficWindowSummaries.projectId, project.id)).run();
21568
22109
  for (const ws of windowSummaries) {
21569
22110
  tx.insert(gaTrafficWindowSummaries).values({
21570
- id: crypto19.randomUUID(),
22111
+ id: crypto20.randomUUID(),
21571
22112
  projectId: project.id,
21572
22113
  windowKey: ws.windowKey,
21573
22114
  periodStart: ws.periodStart,
@@ -21582,7 +22123,7 @@ async function ga4Routes(app, opts) {
21582
22123
  }
21583
22124
  }
21584
22125
  });
21585
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq23(runs.id, runId)).run();
22126
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq24(runs.id, runId)).run();
21586
22127
  const syncedComponents = only ? [
21587
22128
  ...syncTraffic ? ["traffic"] : [],
21588
22129
  ...syncSummary ? ["summary"] : [],
@@ -21611,7 +22152,7 @@ async function ga4Routes(app, opts) {
21611
22152
  } catch (e) {
21612
22153
  const msg = e instanceof Error ? e.message : String(e);
21613
22154
  gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
21614
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(runs.id, runId)).run();
22155
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq24(runs.id, runId)).run();
21615
22156
  throw e;
21616
22157
  }
21617
22158
  });
@@ -21622,11 +22163,11 @@ async function ga4Routes(app, opts) {
21622
22163
  const window = parseWindow(request.query.window);
21623
22164
  const cutoff = windowCutoff(window);
21624
22165
  const cutoffDate = cutoff?.slice(0, 10) ?? null;
21625
- const snapshotConditions = [eq23(gaTrafficSnapshots.projectId, project.id)];
22166
+ const snapshotConditions = [eq24(gaTrafficSnapshots.projectId, project.id)];
21626
22167
  if (cutoffDate) snapshotConditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
21627
- const aiConditions = [eq23(gaAiReferrals.projectId, project.id)];
22168
+ const aiConditions = [eq24(gaAiReferrals.projectId, project.id)];
21628
22169
  if (cutoffDate) aiConditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
21629
- const socialConditions = [eq23(gaSocialReferrals.projectId, project.id)];
22170
+ const socialConditions = [eq24(gaSocialReferrals.projectId, project.id)];
21630
22171
  if (cutoffDate) socialConditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
21631
22172
  const windowSummaryRow = cutoffDate ? app.db.select({
21632
22173
  totalSessions: gaTrafficWindowSummaries.totalSessions,
@@ -21634,42 +22175,42 @@ async function ga4Routes(app, opts) {
21634
22175
  totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
21635
22176
  totalUsers: gaTrafficWindowSummaries.totalUsers
21636
22177
  }).from(gaTrafficWindowSummaries).where(
21637
- and16(
21638
- eq23(gaTrafficWindowSummaries.projectId, project.id),
21639
- eq23(gaTrafficWindowSummaries.windowKey, window)
22178
+ and17(
22179
+ eq24(gaTrafficWindowSummaries.projectId, project.id),
22180
+ eq24(gaTrafficWindowSummaries.windowKey, window)
21640
22181
  )
21641
22182
  ).get() : null;
21642
22183
  const snapshotTotalsRow = cutoffDate && !windowSummaryRow ? app.db.select({
21643
22184
  totalSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
21644
22185
  totalOrganicSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
21645
22186
  totalUsers: sql9`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
21646
- }).from(gaTrafficSnapshots).where(and16(...snapshotConditions)).get() : null;
22187
+ }).from(gaTrafficSnapshots).where(and17(...snapshotConditions)).get() : null;
21647
22188
  const summaryRow = cutoffDate ? windowSummaryRow ?? snapshotTotalsRow : app.db.select({
21648
22189
  totalSessions: gaTrafficSummaries.totalSessions,
21649
22190
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
21650
22191
  totalUsers: gaTrafficSummaries.totalUsers
21651
- }).from(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).get();
22192
+ }).from(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).get();
21652
22193
  const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
21653
22194
  totalDirectSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
21654
- }).from(gaTrafficSnapshots).where(and16(...snapshotConditions)).get();
22195
+ }).from(gaTrafficSnapshots).where(and17(...snapshotConditions)).get();
21655
22196
  const summaryMeta = app.db.select({
21656
22197
  periodStart: gaTrafficSummaries.periodStart,
21657
22198
  periodEnd: gaTrafficSummaries.periodEnd
21658
- }).from(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).get();
22199
+ }).from(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).get();
21659
22200
  const rows = app.db.select({
21660
22201
  landingPage: sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
21661
22202
  sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
21662
22203
  organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
21663
22204
  directSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
21664
22205
  users: sql9`SUM(${gaTrafficSnapshots.users})`
21665
- }).from(gaTrafficSnapshots).where(and16(...snapshotConditions)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
22206
+ }).from(gaTrafficSnapshots).where(and17(...snapshotConditions)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
21666
22207
  const aiReferralRows = app.db.select({
21667
22208
  source: gaAiReferrals.source,
21668
22209
  medium: gaAiReferrals.medium,
21669
22210
  sourceDimension: gaAiReferrals.sourceDimension,
21670
22211
  sessions: sql9`SUM(${gaAiReferrals.sessions})`,
21671
22212
  users: sql9`SUM(${gaAiReferrals.users})`
21672
- }).from(gaAiReferrals).where(and16(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
22213
+ }).from(gaAiReferrals).where(and17(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).all();
21673
22214
  const aiReferralLandingPageRows = app.db.select({
21674
22215
  source: gaAiReferrals.source,
21675
22216
  medium: gaAiReferrals.medium,
@@ -21677,7 +22218,7 @@ async function ga4Routes(app, opts) {
21677
22218
  landingPage: sql9`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
21678
22219
  sessions: sql9`SUM(${gaAiReferrals.sessions})`,
21679
22220
  users: sql9`SUM(${gaAiReferrals.users})`
21680
- }).from(gaAiReferrals).where(and16(...aiConditions)).groupBy(
22221
+ }).from(gaAiReferrals).where(and17(...aiConditions)).groupBy(
21681
22222
  gaAiReferrals.source,
21682
22223
  gaAiReferrals.medium,
21683
22224
  gaAiReferrals.sourceDimension,
@@ -21714,7 +22255,7 @@ async function ga4Routes(app, opts) {
21714
22255
  channelGroup: gaAiReferrals.channelGroup,
21715
22256
  sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
21716
22257
  users: sql9`COALESCE(SUM(${gaAiReferrals.users}), 0)`
21717
- }).from(gaAiReferrals).where(and16(...aiConditions, eq23(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
22258
+ }).from(gaAiReferrals).where(and17(...aiConditions, eq24(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
21718
22259
  const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
21719
22260
  let aiBySessionUsers = 0;
21720
22261
  for (const row of aiBySessionRows) {
@@ -21728,12 +22269,12 @@ async function ga4Routes(app, opts) {
21728
22269
  channelGroup: gaSocialReferrals.channelGroup,
21729
22270
  sessions: sql9`SUM(${gaSocialReferrals.sessions})`,
21730
22271
  users: sql9`SUM(${gaSocialReferrals.users})`
21731
- }).from(gaSocialReferrals).where(and16(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql9`SUM(${gaSocialReferrals.sessions}) DESC`).all();
22272
+ }).from(gaSocialReferrals).where(and17(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql9`SUM(${gaSocialReferrals.sessions}) DESC`).all();
21732
22273
  const socialTotals = app.db.select({
21733
22274
  sessions: sql9`SUM(${gaSocialReferrals.sessions})`,
21734
22275
  users: sql9`SUM(${gaSocialReferrals.users})`
21735
- }).from(gaSocialReferrals).where(and16(...socialConditions)).get();
21736
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).orderBy(desc12(gaTrafficSummaries.syncedAt)).limit(1).get();
22276
+ }).from(gaSocialReferrals).where(and17(...socialConditions)).get();
22277
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq24(gaTrafficSummaries.projectId, project.id)).orderBy(desc12(gaTrafficSummaries.syncedAt)).limit(1).get();
21737
22278
  const total = summaryRow?.totalSessions ?? 0;
21738
22279
  const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
21739
22280
  const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
@@ -21813,7 +22354,7 @@ async function ga4Routes(app, opts) {
21813
22354
  const project = resolveProject(app.db, request.params.name);
21814
22355
  requireGa4Connection(opts, project.name, project.canonicalDomain);
21815
22356
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
21816
- const conditions = [eq23(gaAiReferrals.projectId, project.id)];
22357
+ const conditions = [eq24(gaAiReferrals.projectId, project.id)];
21817
22358
  if (cutoffDate) conditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
21818
22359
  const rows = app.db.select({
21819
22360
  date: gaAiReferrals.date,
@@ -21823,7 +22364,7 @@ async function ga4Routes(app, opts) {
21823
22364
  sourceDimension: gaAiReferrals.sourceDimension,
21824
22365
  sessions: sql9`SUM(${gaAiReferrals.sessions})`,
21825
22366
  users: sql9`SUM(${gaAiReferrals.users})`
21826
- }).from(gaAiReferrals).where(and16(...conditions)).groupBy(
22367
+ }).from(gaAiReferrals).where(and17(...conditions)).groupBy(
21827
22368
  gaAiReferrals.date,
21828
22369
  gaAiReferrals.source,
21829
22370
  gaAiReferrals.medium,
@@ -21836,7 +22377,7 @@ async function ga4Routes(app, opts) {
21836
22377
  const project = resolveProject(app.db, request.params.name);
21837
22378
  requireGa4Connection(opts, project.name, project.canonicalDomain);
21838
22379
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
21839
- const conditions = [eq23(gaSocialReferrals.projectId, project.id)];
22380
+ const conditions = [eq24(gaSocialReferrals.projectId, project.id)];
21840
22381
  if (cutoffDate) conditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
21841
22382
  const rows = app.db.select({
21842
22383
  date: gaSocialReferrals.date,
@@ -21845,7 +22386,7 @@ async function ga4Routes(app, opts) {
21845
22386
  channelGroup: gaSocialReferrals.channelGroup,
21846
22387
  sessions: gaSocialReferrals.sessions,
21847
22388
  users: gaSocialReferrals.users
21848
- }).from(gaSocialReferrals).where(and16(...conditions)).orderBy(gaSocialReferrals.date).all();
22389
+ }).from(gaSocialReferrals).where(and17(...conditions)).orderBy(gaSocialReferrals.date).all();
21849
22390
  return rows;
21850
22391
  });
21851
22392
  app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
@@ -21858,8 +22399,8 @@ async function ga4Routes(app, opts) {
21858
22399
  d.setDate(d.getDate() - n);
21859
22400
  return fmt(d);
21860
22401
  };
21861
- const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and16(
21862
- eq23(gaSocialReferrals.projectId, project.id),
22402
+ const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and17(
22403
+ eq24(gaSocialReferrals.projectId, project.id),
21863
22404
  sql9`${gaSocialReferrals.date} >= ${from}`,
21864
22405
  sql9`${gaSocialReferrals.date} < ${to}`
21865
22406
  )).get();
@@ -21871,16 +22412,16 @@ async function ga4Routes(app, opts) {
21871
22412
  const sourceCurrent = app.db.select({
21872
22413
  source: gaSocialReferrals.source,
21873
22414
  sessions: sql9`SUM(${gaSocialReferrals.sessions})`
21874
- }).from(gaSocialReferrals).where(and16(
21875
- eq23(gaSocialReferrals.projectId, project.id),
22415
+ }).from(gaSocialReferrals).where(and17(
22416
+ eq24(gaSocialReferrals.projectId, project.id),
21876
22417
  sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
21877
22418
  sql9`${gaSocialReferrals.date} < ${fmt(today)}`
21878
22419
  )).groupBy(gaSocialReferrals.source).all();
21879
22420
  const sourcePrev = app.db.select({
21880
22421
  source: gaSocialReferrals.source,
21881
22422
  sessions: sql9`SUM(${gaSocialReferrals.sessions})`
21882
- }).from(gaSocialReferrals).where(and16(
21883
- eq23(gaSocialReferrals.projectId, project.id),
22423
+ }).from(gaSocialReferrals).where(and17(
22424
+ eq24(gaSocialReferrals.projectId, project.id),
21884
22425
  sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
21885
22426
  sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`
21886
22427
  )).groupBy(gaSocialReferrals.source).all();
@@ -21921,16 +22462,16 @@ async function ga4Routes(app, opts) {
21921
22462
  return fmt(d);
21922
22463
  };
21923
22464
  const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
21924
- const sumTotal = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and16(eq23(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
21925
- const sumOrganic = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and16(eq23(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
21926
- const sumDirect = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and16(eq23(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
21927
- const sumAi = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and16(
21928
- eq23(gaAiReferrals.projectId, project.id),
22465
+ const sumTotal = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and17(eq24(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
22466
+ const sumOrganic = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and17(eq24(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
22467
+ const sumDirect = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and17(eq24(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
22468
+ const sumAi = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and17(
22469
+ eq24(gaAiReferrals.projectId, project.id),
21929
22470
  sql9`${gaAiReferrals.date} >= ${from}`,
21930
22471
  sql9`${gaAiReferrals.date} < ${to}`,
21931
- eq23(gaAiReferrals.sourceDimension, "session")
22472
+ eq24(gaAiReferrals.sourceDimension, "session")
21932
22473
  )).get();
21933
- const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and16(eq23(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${from}`, sql9`${gaSocialReferrals.date} < ${to}`)).get();
22474
+ const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and17(eq24(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${from}`, sql9`${gaSocialReferrals.date} < ${to}`)).get();
21934
22475
  const todayStr = fmt(today);
21935
22476
  const buildTrend = (sum) => {
21936
22477
  const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
@@ -21939,17 +22480,17 @@ async function ga4Routes(app, opts) {
21939
22480
  const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
21940
22481
  return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
21941
22482
  };
21942
- const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and16(
21943
- eq23(gaAiReferrals.projectId, project.id),
22483
+ const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and17(
22484
+ eq24(gaAiReferrals.projectId, project.id),
21944
22485
  sql9`${gaAiReferrals.date} >= ${daysAgo(7)}`,
21945
22486
  sql9`${gaAiReferrals.date} < ${todayStr}`,
21946
- eq23(gaAiReferrals.sourceDimension, "session")
22487
+ eq24(gaAiReferrals.sourceDimension, "session")
21947
22488
  )).groupBy(gaAiReferrals.source).all();
21948
- const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and16(
21949
- eq23(gaAiReferrals.projectId, project.id),
22489
+ const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and17(
22490
+ eq24(gaAiReferrals.projectId, project.id),
21950
22491
  sql9`${gaAiReferrals.date} >= ${daysAgo(14)}`,
21951
22492
  sql9`${gaAiReferrals.date} < ${daysAgo(7)}`,
21952
- eq23(gaAiReferrals.sourceDimension, "session")
22493
+ eq24(gaAiReferrals.sourceDimension, "session")
21953
22494
  )).groupBy(gaAiReferrals.source).all();
21954
22495
  const findBiggestMover = (current, prev) => {
21955
22496
  const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
@@ -21965,8 +22506,8 @@ async function ga4Routes(app, opts) {
21965
22506
  }
21966
22507
  return mover;
21967
22508
  };
21968
- const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and16(eq23(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql9`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
21969
- const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and16(eq23(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
22509
+ const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and17(eq24(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql9`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
22510
+ const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and17(eq24(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
21970
22511
  return {
21971
22512
  total: buildTrend(sumTotal),
21972
22513
  organic: buildTrend(sumOrganic),
@@ -21981,14 +22522,14 @@ async function ga4Routes(app, opts) {
21981
22522
  const project = resolveProject(app.db, request.params.name);
21982
22523
  requireGa4Connection(opts, project.name, project.canonicalDomain);
21983
22524
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
21984
- const conditions = [eq23(gaTrafficSnapshots.projectId, project.id)];
22525
+ const conditions = [eq24(gaTrafficSnapshots.projectId, project.id)];
21985
22526
  if (cutoffDate) conditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
21986
22527
  const rows = app.db.select({
21987
22528
  date: gaTrafficSnapshots.date,
21988
22529
  sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
21989
22530
  organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
21990
22531
  users: sql9`SUM(${gaTrafficSnapshots.users})`
21991
- }).from(gaTrafficSnapshots).where(and16(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
22532
+ }).from(gaTrafficSnapshots).where(and17(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
21992
22533
  return rows.map((r) => ({
21993
22534
  date: r.date,
21994
22535
  sessions: r.sessions ?? 0,
@@ -22004,7 +22545,7 @@ async function ga4Routes(app, opts) {
22004
22545
  sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
22005
22546
  organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
22006
22547
  users: sql9`SUM(${gaTrafficSnapshots.users})`
22007
- }).from(gaTrafficSnapshots).where(eq23(gaTrafficSnapshots.projectId, project.id)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
22548
+ }).from(gaTrafficSnapshots).where(eq24(gaTrafficSnapshots.projectId, project.id)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
22008
22549
  return {
22009
22550
  pages: trafficPages.map((r) => ({
22010
22551
  landingPage: r.landingPage,
@@ -22138,7 +22679,7 @@ function parseSchemaPageEntry(entry) {
22138
22679
  }
22139
22680
 
22140
22681
  // ../integration-wordpress/src/wordpress-client.ts
22141
- import crypto20 from "crypto";
22682
+ import crypto21 from "crypto";
22142
22683
  function validateUsername(username) {
22143
22684
  if (!username || typeof username !== "string" || username.trim().length === 0) {
22144
22685
  throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
@@ -22351,7 +22892,7 @@ function buildSnippet(content) {
22351
22892
  return `${text2.slice(0, 157)}...`;
22352
22893
  }
22353
22894
  function contentHash(content) {
22354
- return crypto20.createHash("sha256").update(content).digest("hex");
22895
+ return crypto21.createHash("sha256").update(content).digest("hex");
22355
22896
  }
22356
22897
  function buildAmbiguousSlugMessage(slug, pages) {
22357
22898
  const candidates = pages.map((page) => {
@@ -23651,8 +24192,8 @@ async function wordpressRoutes(app, opts) {
23651
24192
  }
23652
24193
 
23653
24194
  // ../api-routes/src/backlinks.ts
23654
- import crypto21 from "crypto";
23655
- import { and as and18, asc as asc2, desc as desc13, eq as eq24, sql as sql10 } from "drizzle-orm";
24195
+ import crypto22 from "crypto";
24196
+ import { and as and19, asc as asc3, desc as desc13, eq as eq25, sql as sql10 } from "drizzle-orm";
23656
24197
 
23657
24198
  // ../integration-commoncrawl/src/constants.ts
23658
24199
  import os2 from "os";
@@ -24055,7 +24596,7 @@ function pruneCachedRelease(release, opts = {}) {
24055
24596
  }
24056
24597
 
24057
24598
  // ../api-routes/src/backlinks-filter.ts
24058
- import { and as and17, ne as ne3, notLike } from "drizzle-orm";
24599
+ import { and as and18, ne as ne3, notLike } from "drizzle-orm";
24059
24600
  var BACKLINK_FILTER_PATTERNS = [
24060
24601
  "*.google.com",
24061
24602
  "*.googleusercontent.com",
@@ -24078,7 +24619,7 @@ function backlinkCrawlerExclusionClause() {
24078
24619
  conditions.push(ne3(backlinkDomains.linkingDomain, pattern));
24079
24620
  }
24080
24621
  }
24081
- const combined = and17(...conditions);
24622
+ const combined = and18(...conditions);
24082
24623
  if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
24083
24624
  return combined;
24084
24625
  }
@@ -24139,7 +24680,7 @@ function mapRunRow(row) {
24139
24680
  };
24140
24681
  }
24141
24682
  function latestSummaryForProject(db, projectId, release) {
24142
- const condition = release ? and18(eq24(backlinkSummaries.projectId, projectId), eq24(backlinkSummaries.release, release)) : eq24(backlinkSummaries.projectId, projectId);
24683
+ const condition = release ? and19(eq25(backlinkSummaries.projectId, projectId), eq25(backlinkSummaries.release, release)) : eq25(backlinkSummaries.projectId, projectId);
24143
24684
  return db.select().from(backlinkSummaries).where(condition).orderBy(desc13(backlinkSummaries.queriedAt)).limit(1).get();
24144
24685
  }
24145
24686
  function parseExcludeCrawlers(value) {
@@ -24148,11 +24689,11 @@ function parseExcludeCrawlers(value) {
24148
24689
  return lower === "1" || lower === "true" || lower === "yes";
24149
24690
  }
24150
24691
  function computeFilteredSummary(db, base) {
24151
- const baseDomainCondition = and18(
24152
- eq24(backlinkDomains.projectId, base.projectId),
24153
- eq24(backlinkDomains.release, base.release)
24692
+ const baseDomainCondition = and19(
24693
+ eq25(backlinkDomains.projectId, base.projectId),
24694
+ eq25(backlinkDomains.release, base.release)
24154
24695
  );
24155
- const filteredCondition = and18(baseDomainCondition, backlinkCrawlerExclusionClause());
24696
+ const filteredCondition = and19(baseDomainCondition, backlinkCrawlerExclusionClause());
24156
24697
  const unfilteredAgg = db.select({
24157
24698
  count: sql10`count(*)`,
24158
24699
  total: sql10`coalesce(sum(${backlinkDomains.numHosts}), 0)`
@@ -24221,7 +24762,7 @@ async function backlinksRoutes(app, opts) {
24221
24762
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
24222
24763
  );
24223
24764
  }
24224
- const existing = app.db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.release, release)).get();
24765
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq25(ccReleaseSyncs.release, release)).get();
24225
24766
  const now = (/* @__PURE__ */ new Date()).toISOString();
24226
24767
  if (existing) {
24227
24768
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -24232,12 +24773,12 @@ async function backlinksRoutes(app, opts) {
24232
24773
  phaseDetail: null,
24233
24774
  error: null,
24234
24775
  updatedAt: now
24235
- }).where(eq24(ccReleaseSyncs.id, existing.id)).run();
24776
+ }).where(eq25(ccReleaseSyncs.id, existing.id)).run();
24236
24777
  opts.onReleaseSyncRequested(existing.id, release);
24237
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.id, existing.id)).get();
24778
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq25(ccReleaseSyncs.id, existing.id)).get();
24238
24779
  return reply.status(200).send(mapSyncRow(refreshed));
24239
24780
  }
24240
- const id = crypto21.randomUUID();
24781
+ const id = crypto22.randomUUID();
24241
24782
  app.db.insert(ccReleaseSyncs).values({
24242
24783
  id,
24243
24784
  release,
@@ -24246,7 +24787,7 @@ async function backlinksRoutes(app, opts) {
24246
24787
  updatedAt: now
24247
24788
  }).run();
24248
24789
  opts.onReleaseSyncRequested(id, release);
24249
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.id, id)).get();
24790
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq25(ccReleaseSyncs.id, id)).get();
24250
24791
  return reply.status(201).send(mapSyncRow(inserted));
24251
24792
  });
24252
24793
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
@@ -24294,7 +24835,7 @@ async function backlinksRoutes(app, opts) {
24294
24835
  throw validationError("Invalid release id");
24295
24836
  }
24296
24837
  const now = (/* @__PURE__ */ new Date()).toISOString();
24297
- const runId = crypto21.randomUUID();
24838
+ const runId = crypto22.randomUUID();
24298
24839
  app.db.insert(runs).values({
24299
24840
  id: runId,
24300
24841
  projectId: project.id,
@@ -24304,7 +24845,7 @@ async function backlinksRoutes(app, opts) {
24304
24845
  createdAt: now
24305
24846
  }).run();
24306
24847
  opts.onBacklinkExtractRequested(runId, project.id, release);
24307
- const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
24848
+ const run = app.db.select().from(runs).where(eq25(runs.id, runId)).get();
24308
24849
  return reply.status(201).send(mapRunRow(run));
24309
24850
  });
24310
24851
  app.get(
@@ -24328,11 +24869,11 @@ async function backlinksRoutes(app, opts) {
24328
24869
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
24329
24870
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
24330
24871
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
24331
- const baseDomainCondition = and18(
24332
- eq24(backlinkDomains.projectId, project.id),
24333
- eq24(backlinkDomains.release, targetRelease)
24872
+ const baseDomainCondition = and19(
24873
+ eq25(backlinkDomains.projectId, project.id),
24874
+ eq25(backlinkDomains.release, targetRelease)
24334
24875
  );
24335
- const domainCondition = excludeCrawlers ? and18(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
24876
+ const domainCondition = excludeCrawlers ? and19(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
24336
24877
  const totalRow = app.db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(domainCondition).get();
24337
24878
  const rows = app.db.select({
24338
24879
  linkingDomain: backlinkDomains.linkingDomain,
@@ -24353,7 +24894,7 @@ async function backlinksRoutes(app, opts) {
24353
24894
  "/projects/:name/backlinks/history",
24354
24895
  async (request, reply) => {
24355
24896
  const project = resolveProject(app.db, request.params.name);
24356
- const rows = app.db.select().from(backlinkSummaries).where(eq24(backlinkSummaries.projectId, project.id)).orderBy(asc2(backlinkSummaries.queriedAt)).all();
24897
+ const rows = app.db.select().from(backlinkSummaries).where(eq25(backlinkSummaries.projectId, project.id)).orderBy(asc3(backlinkSummaries.queriedAt)).all();
24357
24898
  const response = rows.map((r) => ({
24358
24899
  release: r.release,
24359
24900
  totalLinkingDomains: r.totalLinkingDomains,
@@ -24367,12 +24908,12 @@ async function backlinksRoutes(app, opts) {
24367
24908
  }
24368
24909
 
24369
24910
  // ../api-routes/src/traffic.ts
24370
- import crypto23 from "crypto";
24911
+ import crypto24 from "crypto";
24371
24912
  import { Agent as UndiciAgent } from "undici";
24372
- import { and as and19, desc as desc14, eq as eq25, gte as gte3, lte as lte2, sql as sql11 } from "drizzle-orm";
24913
+ import { and as and20, desc as desc14, eq as eq26, gte as gte4, lte as lte3, sql as sql11 } from "drizzle-orm";
24373
24914
 
24374
24915
  // ../integration-cloud-run/src/auth.ts
24375
- import crypto22 from "crypto";
24916
+ import crypto23 from "crypto";
24376
24917
  var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
24377
24918
  var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
24378
24919
  var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
@@ -24383,6 +24924,8 @@ var CloudRunAuthError = class extends Error {
24383
24924
  this.body = body;
24384
24925
  this.name = "CloudRunAuthError";
24385
24926
  }
24927
+ httpStatus;
24928
+ body;
24386
24929
  };
24387
24930
  function createServiceAccountJwt2(clientEmail, privateKey, scope) {
24388
24931
  if (!clientEmail) throw new CloudRunAuthError("clientEmail is required");
@@ -24401,7 +24944,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
24401
24944
  const headerB64 = encode(header);
24402
24945
  const payloadB64 = encode(payload);
24403
24946
  const signingInput = `${headerB64}.${payloadB64}`;
24404
- const sign = crypto22.createSign("RSA-SHA256");
24947
+ const sign = crypto23.createSign("RSA-SHA256");
24405
24948
  sign.update(signingInput);
24406
24949
  const signature = sign.sign(privateKey, "base64url");
24407
24950
  return `${signingInput}.${signature}`;
@@ -24559,6 +25102,8 @@ var CloudRunLoggingApiError = class extends Error {
24559
25102
  this.body = body;
24560
25103
  this.name = "CloudRunLoggingApiError";
24561
25104
  }
25105
+ status;
25106
+ body;
24562
25107
  };
24563
25108
  function validateAccessToken3(accessToken) {
24564
25109
  if (!accessToken.trim()) {
@@ -27613,6 +28158,8 @@ var WordpressTrafficApiError = class extends Error {
27613
28158
  this.body = body;
27614
28159
  this.name = "WordpressTrafficApiError";
27615
28160
  }
28161
+ status;
28162
+ body;
27616
28163
  };
27617
28164
  function trimRequired(name, value) {
27618
28165
  const trimmed = value.trim();
@@ -27814,6 +28361,9 @@ var VercelLogsApiError = class extends Error {
27814
28361
  this.retryAfterSeconds = retryAfterSeconds;
27815
28362
  this.name = "VercelLogsApiError";
27816
28363
  }
28364
+ status;
28365
+ body;
28366
+ retryAfterSeconds;
27817
28367
  };
27818
28368
  function parseRetryAfter2(headerValue) {
27819
28369
  if (!headerValue) return void 0;
@@ -28177,8 +28727,8 @@ async function runBackfillTask(options) {
28177
28727
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
28178
28728
  try {
28179
28729
  app.db.transaction((tx) => {
28180
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq25(runs.id, runId)).run();
28181
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq25(trafficSources.id, sourceRow.id)).run();
28730
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq26(runs.id, runId)).run();
28731
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq26(trafficSources.id, sourceRow.id)).run();
28182
28732
  });
28183
28733
  } catch {
28184
28734
  }
@@ -28193,7 +28743,7 @@ async function runBackfillTask(options) {
28193
28743
  if (allEvents.length === 0) {
28194
28744
  const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
28195
28745
  try {
28196
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq25(runs.id, runId)).run();
28746
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq26(runs.id, runId)).run();
28197
28747
  } catch {
28198
28748
  }
28199
28749
  return;
@@ -28215,31 +28765,31 @@ async function runBackfillTask(options) {
28215
28765
  try {
28216
28766
  app.db.transaction((tx) => {
28217
28767
  tx.delete(crawlerEventsHourly).where(
28218
- and19(
28219
- eq25(crawlerEventsHourly.sourceId, sourceRow.id),
28220
- gte3(crawlerEventsHourly.tsHour, windowStartIso),
28221
- lte2(crawlerEventsHourly.tsHour, windowEndIso)
28768
+ and20(
28769
+ eq26(crawlerEventsHourly.sourceId, sourceRow.id),
28770
+ gte4(crawlerEventsHourly.tsHour, windowStartIso),
28771
+ lte3(crawlerEventsHourly.tsHour, windowEndIso)
28222
28772
  )
28223
28773
  ).run();
28224
28774
  tx.delete(aiUserFetchEventsHourly).where(
28225
- and19(
28226
- eq25(aiUserFetchEventsHourly.sourceId, sourceRow.id),
28227
- gte3(aiUserFetchEventsHourly.tsHour, windowStartIso),
28228
- lte2(aiUserFetchEventsHourly.tsHour, windowEndIso)
28775
+ and20(
28776
+ eq26(aiUserFetchEventsHourly.sourceId, sourceRow.id),
28777
+ gte4(aiUserFetchEventsHourly.tsHour, windowStartIso),
28778
+ lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
28229
28779
  )
28230
28780
  ).run();
28231
28781
  tx.delete(aiReferralEventsHourly).where(
28232
- and19(
28233
- eq25(aiReferralEventsHourly.sourceId, sourceRow.id),
28234
- gte3(aiReferralEventsHourly.tsHour, windowStartIso),
28235
- lte2(aiReferralEventsHourly.tsHour, windowEndIso)
28782
+ and20(
28783
+ eq26(aiReferralEventsHourly.sourceId, sourceRow.id),
28784
+ gte4(aiReferralEventsHourly.tsHour, windowStartIso),
28785
+ lte3(aiReferralEventsHourly.tsHour, windowEndIso)
28236
28786
  )
28237
28787
  ).run();
28238
28788
  tx.delete(rawEventSamples).where(
28239
- and19(
28240
- eq25(rawEventSamples.sourceId, sourceRow.id),
28241
- gte3(rawEventSamples.ts, windowStartIso),
28242
- lte2(rawEventSamples.ts, windowEndIso)
28789
+ and20(
28790
+ eq26(rawEventSamples.sourceId, sourceRow.id),
28791
+ gte4(rawEventSamples.ts, windowStartIso),
28792
+ lte3(rawEventSamples.ts, windowEndIso)
28243
28793
  )
28244
28794
  ).run();
28245
28795
  for (const bucket of report.crawlerEventsHourly) {
@@ -28302,7 +28852,7 @@ async function runBackfillTask(options) {
28302
28852
  }
28303
28853
  })();
28304
28854
  tx.insert(rawEventSamples).values({
28305
- id: crypto23.randomUUID(),
28855
+ id: crypto24.randomUUID(),
28306
28856
  projectId: project.id,
28307
28857
  sourceId: sourceRow.id,
28308
28858
  ts: sample.observedAt,
@@ -28326,8 +28876,8 @@ async function runBackfillTask(options) {
28326
28876
  lastError: null,
28327
28877
  lastEventIds: newRingBuffer,
28328
28878
  updatedAt: finishedAt
28329
- }).where(eq25(trafficSources.id, sourceRow.id)).run();
28330
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq25(runs.id, runId)).run();
28879
+ }).where(eq26(trafficSources.id, sourceRow.id)).run();
28880
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq26(runs.id, runId)).run();
28331
28881
  });
28332
28882
  } catch (e) {
28333
28883
  markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -28409,7 +28959,7 @@ async function trafficRoutes(app, opts) {
28409
28959
  createdAt: existing?.createdAt ?? now,
28410
28960
  updatedAt: now
28411
28961
  });
28412
- const activeSource = app.db.select().from(trafficSources).where(eq25(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
28962
+ const activeSource = app.db.select().from(trafficSources).where(eq26(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
28413
28963
  const config = {
28414
28964
  gcpProjectId,
28415
28965
  serviceName: serviceName ?? null,
@@ -28425,10 +28975,10 @@ async function trafficRoutes(app, opts) {
28425
28975
  lastError: null,
28426
28976
  configJson: config,
28427
28977
  updatedAt: now
28428
- }).where(eq25(trafficSources.id, activeSource.id)).run();
28429
- sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, activeSource.id)).get();
28978
+ }).where(eq26(trafficSources.id, activeSource.id)).run();
28979
+ sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, activeSource.id)).get();
28430
28980
  } else {
28431
- const newId = crypto23.randomUUID();
28981
+ const newId = crypto24.randomUUID();
28432
28982
  app.db.insert(trafficSources).values({
28433
28983
  id: newId,
28434
28984
  projectId: project.id,
@@ -28443,7 +28993,7 @@ async function trafficRoutes(app, opts) {
28443
28993
  createdAt: now,
28444
28994
  updatedAt: now
28445
28995
  }).run();
28446
- sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, newId)).get();
28996
+ sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, newId)).get();
28447
28997
  }
28448
28998
  writeAuditLog(app.db, {
28449
28999
  projectId: project.id,
@@ -28495,7 +29045,7 @@ async function trafficRoutes(app, opts) {
28495
29045
  createdAt: existing?.createdAt ?? now,
28496
29046
  updatedAt: now
28497
29047
  });
28498
- const activeSource = app.db.select().from(trafficSources).where(eq25(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
29048
+ const activeSource = app.db.select().from(trafficSources).where(eq26(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
28499
29049
  const config = { baseUrl, username };
28500
29050
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
28501
29051
  let sourceRow;
@@ -28506,10 +29056,10 @@ async function trafficRoutes(app, opts) {
28506
29056
  lastError: null,
28507
29057
  configJson: config,
28508
29058
  updatedAt: now
28509
- }).where(eq25(trafficSources.id, activeSource.id)).run();
28510
- sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, activeSource.id)).get();
29059
+ }).where(eq26(trafficSources.id, activeSource.id)).run();
29060
+ sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, activeSource.id)).get();
28511
29061
  } else {
28512
- const newId = crypto23.randomUUID();
29062
+ const newId = crypto24.randomUUID();
28513
29063
  app.db.insert(trafficSources).values({
28514
29064
  id: newId,
28515
29065
  projectId: project.id,
@@ -28524,7 +29074,7 @@ async function trafficRoutes(app, opts) {
28524
29074
  createdAt: now,
28525
29075
  updatedAt: now
28526
29076
  }).run();
28527
- sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, newId)).get();
29077
+ sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, newId)).get();
28528
29078
  }
28529
29079
  writeAuditLog(app.db, {
28530
29080
  projectId: project.id,
@@ -28578,7 +29128,7 @@ async function trafficRoutes(app, opts) {
28578
29128
  createdAt: existing?.createdAt ?? now,
28579
29129
  updatedAt: now
28580
29130
  });
28581
- const activeSource = app.db.select().from(trafficSources).where(eq25(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
29131
+ const activeSource = app.db.select().from(trafficSources).where(eq26(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
28582
29132
  const config = { projectId, teamId, environment };
28583
29133
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
28584
29134
  const { sourceRow, scheduleCreated } = app.db.transaction((tx) => {
@@ -28590,10 +29140,10 @@ async function trafficRoutes(app, opts) {
28590
29140
  lastError: null,
28591
29141
  configJson: config,
28592
29142
  updatedAt: now
28593
- }).where(eq25(trafficSources.id, activeSource.id)).run();
28594
- row = tx.select().from(trafficSources).where(eq25(trafficSources.id, activeSource.id)).get();
29143
+ }).where(eq26(trafficSources.id, activeSource.id)).run();
29144
+ row = tx.select().from(trafficSources).where(eq26(trafficSources.id, activeSource.id)).get();
28595
29145
  } else {
28596
- const newId = crypto23.randomUUID();
29146
+ const newId = crypto24.randomUUID();
28597
29147
  tx.insert(trafficSources).values({
28598
29148
  id: newId,
28599
29149
  projectId: project.id,
@@ -28616,18 +29166,18 @@ async function trafficRoutes(app, opts) {
28616
29166
  createdAt: now,
28617
29167
  updatedAt: now
28618
29168
  }).run();
28619
- row = tx.select().from(trafficSources).where(eq25(trafficSources.id, newId)).get();
29169
+ row = tx.select().from(trafficSources).where(eq26(trafficSources.id, newId)).get();
28620
29170
  }
28621
29171
  const existingSchedule = tx.select().from(schedules).where(
28622
- and19(
28623
- eq25(schedules.projectId, project.id),
28624
- eq25(schedules.kind, SchedulableRunKinds["traffic-sync"])
29172
+ and20(
29173
+ eq26(schedules.projectId, project.id),
29174
+ eq26(schedules.kind, SchedulableRunKinds["traffic-sync"])
28625
29175
  )
28626
29176
  ).get();
28627
29177
  let created = false;
28628
29178
  if (!existingSchedule) {
28629
29179
  tx.insert(schedules).values({
28630
- id: crypto23.randomUUID(),
29180
+ id: crypto24.randomUUID(),
28631
29181
  projectId: project.id,
28632
29182
  kind: SchedulableRunKinds["traffic-sync"],
28633
29183
  cronExpr: DEFAULT_TRAFFIC_SYNC_CRON,
@@ -28670,7 +29220,7 @@ async function trafficRoutes(app, opts) {
28670
29220
  });
28671
29221
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
28672
29222
  const project = resolveProject(app.db, request.params.name);
28673
- const sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, request.params.id)).get();
29223
+ const sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, request.params.id)).get();
28674
29224
  if (!sourceRow || sourceRow.projectId !== project.id) {
28675
29225
  throw notFound("Traffic source", request.params.id);
28676
29226
  }
@@ -28682,7 +29232,7 @@ async function trafficRoutes(app, opts) {
28682
29232
  const windowEnd = /* @__PURE__ */ new Date();
28683
29233
  const startedAt = windowEnd.toISOString();
28684
29234
  const syncStartedAtMs = windowEnd.getTime();
28685
- const runId = crypto23.randomUUID();
29235
+ const runId = crypto24.randomUUID();
28686
29236
  app.db.insert(runs).values({
28687
29237
  id: runId,
28688
29238
  projectId: project.id,
@@ -28696,8 +29246,8 @@ async function trafficRoutes(app, opts) {
28696
29246
  const markFailed = (msg, errorCode) => {
28697
29247
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
28698
29248
  app.db.transaction((tx) => {
28699
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq25(runs.id, runId)).run();
28700
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq25(trafficSources.id, sourceRow.id)).run();
29249
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq26(runs.id, runId)).run();
29250
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq26(trafficSources.id, sourceRow.id)).run();
28701
29251
  });
28702
29252
  try {
28703
29253
  opts.onTrafficSynced?.({
@@ -28778,7 +29328,7 @@ async function trafficRoutes(app, opts) {
28778
29328
  }
28779
29329
  const credential = credentialStore.getConnection(project.name);
28780
29330
  if (!credential) {
28781
- app.db.delete(runs).where(eq25(runs.id, runId)).run();
29331
+ app.db.delete(runs).where(eq26(runs.id, runId)).run();
28782
29332
  throw validationError(
28783
29333
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
28784
29334
  );
@@ -28827,12 +29377,12 @@ async function trafficRoutes(app, opts) {
28827
29377
  auditAction = "traffic.vercel.synced";
28828
29378
  const credentialStore = opts.vercelTrafficCredentialStore;
28829
29379
  if (!credentialStore) {
28830
- app.db.delete(runs).where(eq25(runs.id, runId)).run();
29380
+ app.db.delete(runs).where(eq26(runs.id, runId)).run();
28831
29381
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
28832
29382
  }
28833
29383
  const credential = credentialStore.getConnection(project.name);
28834
29384
  if (!credential) {
28835
- app.db.delete(runs).where(eq25(runs.id, runId)).run();
29385
+ app.db.delete(runs).where(eq26(runs.id, runId)).run();
28836
29386
  throw validationError(
28837
29387
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
28838
29388
  );
@@ -28932,7 +29482,7 @@ async function trafficRoutes(app, opts) {
28932
29482
  let aiReferralHitsCount = 0;
28933
29483
  let unknownHitsCount = 0;
28934
29484
  app.db.transaction((tx) => {
28935
- const latestRow = tx.select().from(trafficSources).where(eq25(trafficSources.id, sourceRow.id)).get();
29485
+ const latestRow = tx.select().from(trafficSources).where(eq26(trafficSources.id, sourceRow.id)).get();
28936
29486
  const previousIds = latestRow.lastEventIds ?? [];
28937
29487
  const seenEventIds = new Set(previousIds);
28938
29488
  const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
@@ -29065,7 +29615,7 @@ async function trafficRoutes(app, opts) {
29065
29615
  }
29066
29616
  })();
29067
29617
  tx.insert(rawEventSamples).values({
29068
- id: crypto23.randomUUID(),
29618
+ id: crypto24.randomUUID(),
29069
29619
  projectId: project.id,
29070
29620
  sourceId: sourceRow.id,
29071
29621
  ts: sample.observedAt,
@@ -29100,8 +29650,8 @@ async function trafficRoutes(app, opts) {
29100
29650
  if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
29101
29651
  sourceUpdate.lastCursor = nextCursor ?? null;
29102
29652
  }
29103
- tx.update(trafficSources).set(sourceUpdate).where(eq25(trafficSources.id, sourceRow.id)).run();
29104
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq25(runs.id, runId)).run();
29653
+ tx.update(trafficSources).set(sourceUpdate).where(eq26(trafficSources.id, sourceRow.id)).run();
29654
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq26(runs.id, runId)).run();
29105
29655
  });
29106
29656
  writeAuditLog(app.db, {
29107
29657
  projectId: project.id,
@@ -29153,7 +29703,7 @@ async function trafficRoutes(app, opts) {
29153
29703
  });
29154
29704
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
29155
29705
  const project = resolveProject(app.db, request.params.name);
29156
- const sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, request.params.id)).get();
29706
+ const sourceRow = app.db.select().from(trafficSources).where(eq26(trafficSources.id, request.params.id)).get();
29157
29707
  if (!sourceRow || sourceRow.projectId !== project.id) {
29158
29708
  throw notFound("Traffic source", request.params.id);
29159
29709
  }
@@ -29303,7 +29853,7 @@ async function trafficRoutes(app, opts) {
29303
29853
  };
29304
29854
  }
29305
29855
  const startedAt = windowEnd.toISOString();
29306
- const runId = crypto23.randomUUID();
29856
+ const runId = crypto24.randomUUID();
29307
29857
  app.db.insert(runs).values({
29308
29858
  id: runId,
29309
29859
  projectId: project.id,
@@ -29338,34 +29888,34 @@ async function trafficRoutes(app, opts) {
29338
29888
  });
29339
29889
  function buildSourceDetail(projectId, row, since) {
29340
29890
  const crawlerTotals = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
29341
- and19(
29342
- eq25(crawlerEventsHourly.sourceId, row.id),
29343
- gte3(crawlerEventsHourly.tsHour, since)
29891
+ and20(
29892
+ eq26(crawlerEventsHourly.sourceId, row.id),
29893
+ gte4(crawlerEventsHourly.tsHour, since)
29344
29894
  )
29345
29895
  ).get();
29346
29896
  const aiUserFetchTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
29347
- and19(
29348
- eq25(aiUserFetchEventsHourly.sourceId, row.id),
29349
- gte3(aiUserFetchEventsHourly.tsHour, since)
29897
+ and20(
29898
+ eq26(aiUserFetchEventsHourly.sourceId, row.id),
29899
+ gte4(aiUserFetchEventsHourly.tsHour, since)
29350
29900
  )
29351
29901
  ).get();
29352
29902
  const aiTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
29353
- and19(
29354
- eq25(aiReferralEventsHourly.sourceId, row.id),
29355
- gte3(aiReferralEventsHourly.tsHour, since)
29903
+ and20(
29904
+ eq26(aiReferralEventsHourly.sourceId, row.id),
29905
+ gte4(aiReferralEventsHourly.tsHour, since)
29356
29906
  )
29357
29907
  ).get();
29358
29908
  const sampleTotals = app.db.select({ total: sql11`COUNT(*)` }).from(rawEventSamples).where(
29359
- and19(
29360
- eq25(rawEventSamples.sourceId, row.id),
29361
- gte3(rawEventSamples.ts, since)
29909
+ and20(
29910
+ eq26(rawEventSamples.sourceId, row.id),
29911
+ gte4(rawEventSamples.ts, since)
29362
29912
  )
29363
29913
  ).get();
29364
29914
  const latestRun = app.db.select().from(runs).where(
29365
- and19(
29366
- eq25(runs.projectId, projectId),
29367
- eq25(runs.kind, RunKinds["traffic-sync"]),
29368
- eq25(runs.sourceId, row.id)
29915
+ and20(
29916
+ eq26(runs.projectId, projectId),
29917
+ eq26(runs.kind, RunKinds["traffic-sync"]),
29918
+ eq26(runs.sourceId, row.id)
29369
29919
  )
29370
29920
  ).orderBy(desc14(runs.startedAt)).limit(1).get();
29371
29921
  return {
@@ -29393,7 +29943,7 @@ async function trafficRoutes(app, opts) {
29393
29943
  "`advanceToNow` must be `true`. There is no implicit reset."
29394
29944
  );
29395
29945
  }
29396
- const sourceRow = app.db.select().from(trafficSources).where(and19(eq25(trafficSources.projectId, project.id), eq25(trafficSources.id, request.params.id))).get();
29946
+ const sourceRow = app.db.select().from(trafficSources).where(and20(eq26(trafficSources.projectId, project.id), eq26(trafficSources.id, request.params.id))).get();
29397
29947
  if (!sourceRow) {
29398
29948
  throw notFound("traffic source", request.params.id);
29399
29949
  }
@@ -29410,7 +29960,7 @@ async function trafficRoutes(app, opts) {
29410
29960
  status: TrafficSourceStatuses.connected,
29411
29961
  lastError: null,
29412
29962
  updatedAt: now
29413
- }).where(eq25(trafficSources.id, sourceRow.id)).run();
29963
+ }).where(eq26(trafficSources.id, sourceRow.id)).run();
29414
29964
  writeAuditLog(tx, auditFromRequest(request, {
29415
29965
  projectId: project.id,
29416
29966
  actor: "api",
@@ -29418,20 +29968,20 @@ async function trafficRoutes(app, opts) {
29418
29968
  entityType: "traffic_source",
29419
29969
  entityId: sourceRow.id
29420
29970
  }));
29421
- updatedRow = tx.select().from(trafficSources).where(eq25(trafficSources.id, sourceRow.id)).get();
29971
+ updatedRow = tx.select().from(trafficSources).where(eq26(trafficSources.id, sourceRow.id)).get();
29422
29972
  });
29423
29973
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
29424
29974
  });
29425
29975
  app.get("/projects/:name/traffic/sources", async (request) => {
29426
29976
  const project = resolveProject(app.db, request.params.name);
29427
- const rows = app.db.select().from(trafficSources).where(eq25(trafficSources.projectId, project.id)).orderBy(desc14(trafficSources.createdAt)).all();
29977
+ const rows = app.db.select().from(trafficSources).where(eq26(trafficSources.projectId, project.id)).orderBy(desc14(trafficSources.createdAt)).all();
29428
29978
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
29429
29979
  const response = { sources };
29430
29980
  return response;
29431
29981
  });
29432
29982
  app.get("/projects/:name/traffic/status", async (request) => {
29433
29983
  const project = resolveProject(app.db, request.params.name);
29434
- const rows = app.db.select().from(trafficSources).where(eq25(trafficSources.projectId, project.id)).orderBy(desc14(trafficSources.createdAt)).all();
29984
+ const rows = app.db.select().from(trafficSources).where(eq26(trafficSources.projectId, project.id)).orderBy(desc14(trafficSources.createdAt)).all();
29435
29985
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
29436
29986
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
29437
29987
  const response = { sources };
@@ -29441,7 +29991,7 @@ async function trafficRoutes(app, opts) {
29441
29991
  "/projects/:name/traffic/sources/:id",
29442
29992
  async (request) => {
29443
29993
  const project = resolveProject(app.db, request.params.name);
29444
- const row = app.db.select().from(trafficSources).where(eq25(trafficSources.id, request.params.id)).get();
29994
+ const row = app.db.select().from(trafficSources).where(eq26(trafficSources.id, request.params.id)).get();
29445
29995
  if (!row || row.projectId !== project.id) {
29446
29996
  throw notFound("Traffic source", request.params.id);
29447
29997
  }
@@ -29492,12 +30042,12 @@ async function trafficRoutes(app, opts) {
29492
30042
  let aiReferralTotal = 0;
29493
30043
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
29494
30044
  const crawlerFilters = [
29495
- eq25(crawlerEventsHourly.projectId, project.id),
29496
- gte3(crawlerEventsHourly.tsHour, sinceIso),
29497
- lte2(crawlerEventsHourly.tsHour, untilIso)
30045
+ eq26(crawlerEventsHourly.projectId, project.id),
30046
+ gte4(crawlerEventsHourly.tsHour, sinceIso),
30047
+ lte3(crawlerEventsHourly.tsHour, untilIso)
29498
30048
  ];
29499
- if (sourceIdParam) crawlerFilters.push(eq25(crawlerEventsHourly.sourceId, sourceIdParam));
29500
- const crawlerWhere = and19(...crawlerFilters);
30049
+ if (sourceIdParam) crawlerFilters.push(eq26(crawlerEventsHourly.sourceId, sourceIdParam));
30050
+ const crawlerWhere = and20(...crawlerFilters);
29501
30051
  const total = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
29502
30052
  crawlerTotal = Number(total?.total ?? 0);
29503
30053
  const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc14(crawlerEventsHourly.tsHour)).limit(limit).all();
@@ -29517,12 +30067,12 @@ async function trafficRoutes(app, opts) {
29517
30067
  }
29518
30068
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
29519
30069
  const userFetchFilters = [
29520
- eq25(aiUserFetchEventsHourly.projectId, project.id),
29521
- gte3(aiUserFetchEventsHourly.tsHour, sinceIso),
29522
- lte2(aiUserFetchEventsHourly.tsHour, untilIso)
30070
+ eq26(aiUserFetchEventsHourly.projectId, project.id),
30071
+ gte4(aiUserFetchEventsHourly.tsHour, sinceIso),
30072
+ lte3(aiUserFetchEventsHourly.tsHour, untilIso)
29523
30073
  ];
29524
- if (sourceIdParam) userFetchFilters.push(eq25(aiUserFetchEventsHourly.sourceId, sourceIdParam));
29525
- const userFetchWhere = and19(...userFetchFilters);
30074
+ if (sourceIdParam) userFetchFilters.push(eq26(aiUserFetchEventsHourly.sourceId, sourceIdParam));
30075
+ const userFetchWhere = and20(...userFetchFilters);
29526
30076
  const total = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
29527
30077
  aiUserFetchTotal = Number(total?.total ?? 0);
29528
30078
  const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc14(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
@@ -29542,12 +30092,12 @@ async function trafficRoutes(app, opts) {
29542
30092
  }
29543
30093
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
29544
30094
  const aiFilters = [
29545
- eq25(aiReferralEventsHourly.projectId, project.id),
29546
- gte3(aiReferralEventsHourly.tsHour, sinceIso),
29547
- lte2(aiReferralEventsHourly.tsHour, untilIso)
30095
+ eq26(aiReferralEventsHourly.projectId, project.id),
30096
+ gte4(aiReferralEventsHourly.tsHour, sinceIso),
30097
+ lte3(aiReferralEventsHourly.tsHour, untilIso)
29548
30098
  ];
29549
- if (sourceIdParam) aiFilters.push(eq25(aiReferralEventsHourly.sourceId, sourceIdParam));
29550
- const aiWhere = and19(...aiFilters);
30099
+ if (sourceIdParam) aiFilters.push(eq26(aiReferralEventsHourly.sourceId, sourceIdParam));
30100
+ const aiWhere = and20(...aiFilters);
29551
30101
  const total = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
29552
30102
  aiReferralTotal = Number(total?.total ?? 0);
29553
30103
  const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc14(aiReferralEventsHourly.tsHour)).limit(limit).all();
@@ -29583,7 +30133,7 @@ async function trafficRoutes(app, opts) {
29583
30133
  }
29584
30134
 
29585
30135
  // ../api-routes/src/doctor/checks/agent.ts
29586
- import crypto24 from "crypto";
30136
+ import crypto25 from "crypto";
29587
30137
  import fs6 from "fs";
29588
30138
  import path7 from "path";
29589
30139
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -29736,7 +30286,7 @@ function isInstalled(dir) {
29736
30286
  }
29737
30287
  function hashInstalledFile(filePath) {
29738
30288
  try {
29739
- return crypto24.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
30289
+ return crypto25.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
29740
30290
  } catch {
29741
30291
  return void 0;
29742
30292
  }
@@ -29897,7 +30447,7 @@ var BING_AUTH_CHECKS = [
29897
30447
  ];
29898
30448
 
29899
30449
  // ../api-routes/src/doctor/checks/content.ts
29900
- import { eq as eq26 } from "drizzle-orm";
30450
+ import { eq as eq27 } from "drizzle-orm";
29901
30451
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
29902
30452
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
29903
30453
  function skippedNoProject() {
@@ -29910,7 +30460,7 @@ function skippedNoProject() {
29910
30460
  }
29911
30461
  function loadProject(ctx) {
29912
30462
  if (!ctx.project) return null;
29913
- return ctx.db.select().from(projects).where(eq26(projects.id, ctx.project.id)).get() ?? null;
30463
+ return ctx.db.select().from(projects).where(eq27(projects.id, ctx.project.id)).get() ?? null;
29914
30464
  }
29915
30465
  function percent(value) {
29916
30466
  return Math.round(value * 100);
@@ -30001,6 +30551,123 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
30001
30551
  CONTENT_CHECKS.map((check) => [check.id, check])
30002
30552
  );
30003
30553
 
30554
+ // ../api-routes/src/doctor/checks/ads.ts
30555
+ import { eq as eq28 } from "drizzle-orm";
30556
+ var RECENT_SYNC_WARN_DAYS = 7;
30557
+ var RECENT_SYNC_FAIL_DAYS = 30;
30558
+ var adsConnectionCheck = {
30559
+ id: "ads.auth.connection",
30560
+ category: CheckCategories.auth,
30561
+ scope: CheckScopes.project,
30562
+ title: "OpenAI ads connection",
30563
+ run: (ctx) => {
30564
+ if (!ctx.project) {
30565
+ return {
30566
+ status: CheckStatuses.skipped,
30567
+ code: "ads.auth.no-project",
30568
+ summary: "Project context required.",
30569
+ remediation: null
30570
+ };
30571
+ }
30572
+ const row = ctx.db.select().from(adsConnections).where(eq28(adsConnections.projectId, ctx.project.id)).get();
30573
+ if (!row) {
30574
+ return {
30575
+ status: CheckStatuses.skipped,
30576
+ code: "ads.auth.not-connected",
30577
+ summary: "No OpenAI ads connection for this project.",
30578
+ remediation: null
30579
+ };
30580
+ }
30581
+ if (!ctx.adsCredentialStore) {
30582
+ return {
30583
+ status: CheckStatuses.skipped,
30584
+ code: "ads.auth.store-unavailable",
30585
+ summary: "No ads credential store configured for this deployment.",
30586
+ remediation: null
30587
+ };
30588
+ }
30589
+ const cfg = ctx.adsCredentialStore.getConnection(ctx.project.name);
30590
+ if (!cfg?.apiKey) {
30591
+ return {
30592
+ status: CheckStatuses.fail,
30593
+ code: "ads.auth.missing-key",
30594
+ summary: "An ads connection row exists but no SDK key is stored in the local config.",
30595
+ remediation: `Re-run \`canonry ads connect ${ctx.project.name} --api-key <sdk-key>\` to restore the credential.`,
30596
+ details: { adAccountId: row.adAccountId }
30597
+ };
30598
+ }
30599
+ return {
30600
+ status: CheckStatuses.ok,
30601
+ code: "ads.auth.ok",
30602
+ summary: `Connected to ad account ${row.displayName ?? row.adAccountId}.`,
30603
+ remediation: null,
30604
+ details: { adAccountId: row.adAccountId }
30605
+ };
30606
+ }
30607
+ };
30608
+ var adsRecentSyncCheck = {
30609
+ id: "ads.data.recent-sync",
30610
+ category: CheckCategories.integrations,
30611
+ scope: CheckScopes.project,
30612
+ title: "OpenAI ads recent sync",
30613
+ run: (ctx) => {
30614
+ if (!ctx.project) {
30615
+ return {
30616
+ status: CheckStatuses.skipped,
30617
+ code: "ads.data.no-project",
30618
+ summary: "Project context required.",
30619
+ remediation: null
30620
+ };
30621
+ }
30622
+ const row = ctx.db.select().from(adsConnections).where(eq28(adsConnections.projectId, ctx.project.id)).get();
30623
+ if (!row) {
30624
+ return {
30625
+ status: CheckStatuses.skipped,
30626
+ code: "ads.data.not-connected",
30627
+ summary: "No OpenAI ads connection for this project.",
30628
+ remediation: null
30629
+ };
30630
+ }
30631
+ if (!row.lastSyncedAt) {
30632
+ return {
30633
+ status: CheckStatuses.warn,
30634
+ code: "ads.data.never-synced",
30635
+ summary: "The connected ad account has never been synced.",
30636
+ remediation: `Run \`canonry ads sync ${ctx.project.name}\` (and schedule it: \`canonry schedule set ${ctx.project.name} --kind ads-sync --preset daily\`).`
30637
+ };
30638
+ }
30639
+ const syncedAtMs = new Date(row.lastSyncedAt).getTime();
30640
+ const ageDays = (Date.now() - syncedAtMs) / (1e3 * 60 * 60 * 24);
30641
+ const details = { lastSyncedAt: row.lastSyncedAt, ageDays: Math.round(ageDays) };
30642
+ if (ageDays > RECENT_SYNC_FAIL_DAYS) {
30643
+ return {
30644
+ status: CheckStatuses.fail,
30645
+ code: "ads.data.stale",
30646
+ summary: `Last ads sync was ${Math.round(ageDays)} days ago (> ${RECENT_SYNC_FAIL_DAYS}d).`,
30647
+ remediation: `Run \`canonry ads sync ${ctx.project.name}\` and check the ads-sync schedule.`,
30648
+ details
30649
+ };
30650
+ }
30651
+ if (ageDays > RECENT_SYNC_WARN_DAYS) {
30652
+ return {
30653
+ status: CheckStatuses.warn,
30654
+ code: "ads.data.aging",
30655
+ summary: `Last ads sync was ${Math.round(ageDays)} days ago (> ${RECENT_SYNC_WARN_DAYS}d).`,
30656
+ remediation: `Schedule daily syncs: \`canonry schedule set ${ctx.project.name} --kind ads-sync --preset daily\`.`,
30657
+ details
30658
+ };
30659
+ }
30660
+ return {
30661
+ status: CheckStatuses.ok,
30662
+ code: "ads.data.ok",
30663
+ summary: `Last ads sync ${Math.round(ageDays)} day(s) ago.`,
30664
+ remediation: null,
30665
+ details
30666
+ };
30667
+ }
30668
+ };
30669
+ var ADS_CHECKS = [adsConnectionCheck, adsRecentSyncCheck];
30670
+
30004
30671
  // ../api-routes/src/doctor/checks/ga-auth.ts
30005
30672
  async function checkServiceAccount(conn) {
30006
30673
  if (!conn.propertyId) {
@@ -30143,9 +30810,9 @@ var ga4ConnectionCheck = {
30143
30810
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
30144
30811
 
30145
30812
  // ../api-routes/src/doctor/checks/gbp-auth.ts
30146
- import { and as and20, eq as eq27 } from "drizzle-orm";
30147
- var RECENT_SYNC_WARN_DAYS = 7;
30148
- var RECENT_SYNC_FAIL_DAYS = 30;
30813
+ import { and as and21, eq as eq29 } from "drizzle-orm";
30814
+ var RECENT_SYNC_WARN_DAYS2 = 7;
30815
+ var RECENT_SYNC_FAIL_DAYS2 = 30;
30149
30816
  function skippedNoProject2() {
30150
30817
  return {
30151
30818
  status: CheckStatuses.skipped,
@@ -30376,7 +31043,7 @@ var recentSyncCheck = {
30376
31043
  title: "GBP recent sync",
30377
31044
  run: (ctx) => {
30378
31045
  if (!ctx.project) return skippedNoProject2();
30379
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and20(eq27(gbpLocations.projectId, ctx.project.id), eq27(gbpLocations.selected, true))).all();
31046
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and21(eq29(gbpLocations.projectId, ctx.project.id), eq29(gbpLocations.selected, true))).all();
30380
31047
  if (selected.length === 0) {
30381
31048
  return {
30382
31049
  status: CheckStatuses.skipped,
@@ -30398,20 +31065,20 @@ var recentSyncCheck = {
30398
31065
  const newest = Math.max(...syncTimes);
30399
31066
  const ageDays = (Date.now() - newest) / (1e3 * 60 * 60 * 24);
30400
31067
  const details = { selectedLocations: selected.length, newestSyncAgeDays: Math.round(ageDays) };
30401
- if (ageDays > RECENT_SYNC_FAIL_DAYS) {
31068
+ if (ageDays > RECENT_SYNC_FAIL_DAYS2) {
30402
31069
  return {
30403
31070
  status: CheckStatuses.fail,
30404
31071
  code: "gbp.data.stale",
30405
- summary: `Most recent GBP sync was ${Math.round(ageDays)} days ago (> ${RECENT_SYNC_FAIL_DAYS}d).`,
31072
+ summary: `Most recent GBP sync was ${Math.round(ageDays)} days ago (> ${RECENT_SYNC_FAIL_DAYS2}d).`,
30406
31073
  remediation: `Run \`canonry gbp sync ${ctx.project.name}\` or set a gbp-sync schedule.`,
30407
31074
  details
30408
31075
  };
30409
31076
  }
30410
- if (ageDays > RECENT_SYNC_WARN_DAYS) {
31077
+ if (ageDays > RECENT_SYNC_WARN_DAYS2) {
30411
31078
  return {
30412
31079
  status: CheckStatuses.warn,
30413
31080
  code: "gbp.data.aging",
30414
- summary: `Most recent GBP sync was ${Math.round(ageDays)} days ago (> ${RECENT_SYNC_WARN_DAYS}d).`,
31081
+ summary: `Most recent GBP sync was ${Math.round(ageDays)} days ago (> ${RECENT_SYNC_WARN_DAYS2}d).`,
30415
31082
  remediation: `Run \`canonry gbp sync ${ctx.project.name}\` or set a gbp-sync schedule to keep data fresh.`,
30416
31083
  details
30417
31084
  };
@@ -30436,7 +31103,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
30436
31103
  );
30437
31104
 
30438
31105
  // ../api-routes/src/doctor/checks/places.ts
30439
- import { eq as eq28 } from "drizzle-orm";
31106
+ import { eq as eq30 } from "drizzle-orm";
30440
31107
  var apiKeyCheck = {
30441
31108
  id: "gbp.places.api-key",
30442
31109
  category: CheckCategories.auth,
@@ -30481,7 +31148,7 @@ var apiKeyCheck = {
30481
31148
  details: { tier: cfg.tier }
30482
31149
  };
30483
31150
  }
30484
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq28(gbpLocations.projectId, ctx.project.id)).all();
31151
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq30(gbpLocations.projectId, ctx.project.id)).all();
30485
31152
  const selected = rows.filter((r) => r.selected);
30486
31153
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
30487
31154
  const details = {
@@ -30932,7 +31599,7 @@ var RUNTIME_STATE_CHECKS = [
30932
31599
  ];
30933
31600
 
30934
31601
  // ../api-routes/src/doctor/checks/traffic-source.ts
30935
- import { and as and21, eq as eq29, gte as gte4, ne as ne4, sql as sql12 } from "drizzle-orm";
31602
+ import { and as and22, eq as eq31, gte as gte5, ne as ne4, sql as sql12 } from "drizzle-orm";
30936
31603
  var RECENT_DATA_WARN_DAYS = 7;
30937
31604
  var RECENT_DATA_FAIL_DAYS = 30;
30938
31605
  function skippedNoProject4() {
@@ -30946,8 +31613,8 @@ function skippedNoProject4() {
30946
31613
  function loadProbes(ctx) {
30947
31614
  if (!ctx.project) return [];
30948
31615
  const rows = ctx.db.select().from(trafficSources).where(
30949
- and21(
30950
- eq29(trafficSources.projectId, ctx.project.id),
31616
+ and22(
31617
+ eq31(trafficSources.projectId, ctx.project.id),
30951
31618
  ne4(trafficSources.status, TrafficSourceStatuses.archived)
30952
31619
  )
30953
31620
  ).all();
@@ -31027,17 +31694,17 @@ var recentDataCheck = {
31027
31694
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
31028
31695
  const recentCrawlers = Number(
31029
31696
  ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
31030
- and21(
31031
- eq29(crawlerEventsHourly.projectId, ctx.project.id),
31032
- gte4(crawlerEventsHourly.tsHour, warnCutoff)
31697
+ and22(
31698
+ eq31(crawlerEventsHourly.projectId, ctx.project.id),
31699
+ gte5(crawlerEventsHourly.tsHour, warnCutoff)
31033
31700
  )
31034
31701
  ).get()?.total ?? 0
31035
31702
  );
31036
31703
  const recentReferrals = Number(
31037
31704
  ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
31038
- and21(
31039
- eq29(aiReferralEventsHourly.projectId, ctx.project.id),
31040
- gte4(aiReferralEventsHourly.tsHour, warnCutoff)
31705
+ and22(
31706
+ eq31(aiReferralEventsHourly.projectId, ctx.project.id),
31707
+ gte5(aiReferralEventsHourly.tsHour, warnCutoff)
31041
31708
  )
31042
31709
  ).get()?.total ?? 0
31043
31710
  );
@@ -31051,17 +31718,17 @@ var recentDataCheck = {
31051
31718
  }
31052
31719
  const olderCrawlers = Number(
31053
31720
  ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
31054
- and21(
31055
- eq29(crawlerEventsHourly.projectId, ctx.project.id),
31056
- gte4(crawlerEventsHourly.tsHour, failCutoff)
31721
+ and22(
31722
+ eq31(crawlerEventsHourly.projectId, ctx.project.id),
31723
+ gte5(crawlerEventsHourly.tsHour, failCutoff)
31057
31724
  )
31058
31725
  ).get()?.total ?? 0
31059
31726
  );
31060
31727
  const olderReferrals = Number(
31061
31728
  ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
31062
- and21(
31063
- eq29(aiReferralEventsHourly.projectId, ctx.project.id),
31064
- gte4(aiReferralEventsHourly.tsHour, failCutoff)
31729
+ and22(
31730
+ eq31(aiReferralEventsHourly.projectId, ctx.project.id),
31731
+ gte5(aiReferralEventsHourly.tsHour, failCutoff)
31065
31732
  )
31066
31733
  ).get()?.total ?? 0
31067
31734
  );
@@ -31345,6 +32012,7 @@ var ALL_CHECKS = [
31345
32012
  ...BING_AUTH_CHECKS,
31346
32013
  ...WORDPRESS_PUBLISH_CHECKS,
31347
32014
  ...GA_AUTH_CHECKS,
32015
+ ...ADS_CHECKS,
31348
32016
  ...PROVIDERS_CHECKS,
31349
32017
  ...TRAFFIC_SOURCE_CHECKS,
31350
32018
  ...CONTENT_CHECKS,
@@ -31431,6 +32099,7 @@ async function doctorRoutes(app, opts) {
31431
32099
  bingConnectionStore: opts.bingConnectionStore,
31432
32100
  wordpressConnectionStore: opts.wordpressConnectionStore,
31433
32101
  ga4CredentialStore: opts.ga4CredentialStore,
32102
+ adsCredentialStore: opts.adsCredentialStore,
31434
32103
  getGoogleAuthConfig: opts.getGoogleAuthConfig,
31435
32104
  getPlacesConfig: opts.getPlacesConfig,
31436
32105
  redirectUri,
@@ -31456,6 +32125,7 @@ async function doctorRoutes(app, opts) {
31456
32125
  bingConnectionStore: opts.bingConnectionStore,
31457
32126
  wordpressConnectionStore: opts.wordpressConnectionStore,
31458
32127
  ga4CredentialStore: opts.ga4CredentialStore,
32128
+ adsCredentialStore: opts.adsCredentialStore,
31459
32129
  getGoogleAuthConfig: opts.getGoogleAuthConfig,
31460
32130
  getPlacesConfig: opts.getPlacesConfig,
31461
32131
  redirectUri,
@@ -31469,8 +32139,8 @@ async function doctorRoutes(app, opts) {
31469
32139
  }
31470
32140
 
31471
32141
  // ../api-routes/src/discovery/routes.ts
31472
- import crypto25 from "crypto";
31473
- import { and as and22, desc as desc15, eq as eq30, gte as gte5, inArray as inArray10 } from "drizzle-orm";
32142
+ import crypto26 from "crypto";
32143
+ import { and as and23, desc as desc15, eq as eq32, gte as gte6, inArray as inArray11 } from "drizzle-orm";
31474
32144
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
31475
32145
  async function discoveryRoutes(app, opts) {
31476
32146
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -31502,21 +32172,21 @@ async function discoveryRoutes(app, opts) {
31502
32172
  const now = (/* @__PURE__ */ new Date()).toISOString();
31503
32173
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
31504
32174
  const decision = app.db.transaction((tx) => {
31505
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and22(
31506
- eq30(discoverySessions.projectId, project.id),
31507
- eq30(discoverySessions.icpDescription, icpDescription),
31508
- inArray10(discoverySessions.status, [
32175
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and23(
32176
+ eq32(discoverySessions.projectId, project.id),
32177
+ eq32(discoverySessions.icpDescription, icpDescription),
32178
+ inArray11(discoverySessions.status, [
31509
32179
  DiscoverySessionStatuses.queued,
31510
32180
  DiscoverySessionStatuses.seeding,
31511
32181
  DiscoverySessionStatuses.probing
31512
32182
  ]),
31513
- gte5(discoverySessions.createdAt, ageFloorIso)
32183
+ gte6(discoverySessions.createdAt, ageFloorIso)
31514
32184
  )).orderBy(desc15(discoverySessions.createdAt)).get();
31515
32185
  if (existing && existing.runId) {
31516
32186
  return { reused: true, sessionId: existing.id, runId: existing.runId };
31517
32187
  }
31518
- const sessionId = crypto25.randomUUID();
31519
- const runId = crypto25.randomUUID();
32188
+ const sessionId = crypto26.randomUUID();
32189
+ const runId = crypto26.randomUUID();
31520
32190
  tx.insert(discoverySessions).values({
31521
32191
  id: sessionId,
31522
32192
  projectId: project.id,
@@ -31574,7 +32244,7 @@ async function discoveryRoutes(app, opts) {
31574
32244
  const project = resolveProject(app.db, request.params.name);
31575
32245
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
31576
32246
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
31577
- const rows = app.db.select().from(discoverySessions).where(eq30(discoverySessions.projectId, project.id)).orderBy(desc15(discoverySessions.createdAt)).limit(limit).all();
32247
+ const rows = app.db.select().from(discoverySessions).where(eq32(discoverySessions.projectId, project.id)).orderBy(desc15(discoverySessions.createdAt)).limit(limit).all();
31578
32248
  return reply.send(rows.map(serializeSession));
31579
32249
  }
31580
32250
  );
@@ -31582,11 +32252,11 @@ async function discoveryRoutes(app, opts) {
31582
32252
  "/projects/:name/discover/sessions/:id",
31583
32253
  async (request, reply) => {
31584
32254
  const project = resolveProject(app.db, request.params.name);
31585
- const session = app.db.select().from(discoverySessions).where(eq30(discoverySessions.id, request.params.id)).get();
32255
+ const session = app.db.select().from(discoverySessions).where(eq32(discoverySessions.id, request.params.id)).get();
31586
32256
  if (!session || session.projectId !== project.id) {
31587
32257
  throw notFound("Discovery session", request.params.id);
31588
32258
  }
31589
- const probeRows = app.db.select().from(discoveryProbes).where(eq30(discoveryProbes.sessionId, session.id)).all();
32259
+ const probeRows = app.db.select().from(discoveryProbes).where(eq32(discoveryProbes.sessionId, session.id)).all();
31590
32260
  const detail = {
31591
32261
  ...serializeSession(session),
31592
32262
  probes: probeRows.map(serializeProbe)
@@ -31598,12 +32268,12 @@ async function discoveryRoutes(app, opts) {
31598
32268
  "/projects/:name/discover/sessions/:id/promote",
31599
32269
  async (request, reply) => {
31600
32270
  const project = resolveProject(app.db, request.params.name);
31601
- const session = app.db.select().from(discoverySessions).where(eq30(discoverySessions.id, request.params.id)).get();
32271
+ const session = app.db.select().from(discoverySessions).where(eq32(discoverySessions.id, request.params.id)).get();
31602
32272
  if (!session || session.projectId !== project.id) {
31603
32273
  throw notFound("Discovery session", request.params.id);
31604
32274
  }
31605
- const probeRows = app.db.select().from(discoveryProbes).where(eq30(discoveryProbes.sessionId, session.id)).all();
31606
- const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq30(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
32275
+ const probeRows = app.db.select().from(discoveryProbes).where(eq32(discoveryProbes.sessionId, session.id)).all();
32276
+ const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq32(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
31607
32277
  const seenCompetitors = new Set(existingCompetitors);
31608
32278
  const cited = /* @__PURE__ */ new Set();
31609
32279
  const aspirational = /* @__PURE__ */ new Set();
@@ -31632,7 +32302,7 @@ async function discoveryRoutes(app, opts) {
31632
32302
  );
31633
32303
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
31634
32304
  const project = resolveProject(app.db, request.params.name);
31635
- const session = app.db.select().from(discoverySessions).where(eq30(discoverySessions.id, request.params.id)).get();
32305
+ const session = app.db.select().from(discoverySessions).where(eq32(discoverySessions.id, request.params.id)).get();
31636
32306
  if (!session || session.projectId !== project.id) {
31637
32307
  throw notFound("Discovery session", request.params.id);
31638
32308
  }
@@ -31655,7 +32325,7 @@ async function discoveryRoutes(app, opts) {
31655
32325
  const bucketSet = new Set(buckets);
31656
32326
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
31657
32327
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
31658
- const probeRows = app.db.select().from(discoveryProbes).where(eq30(discoveryProbes.sessionId, session.id)).all();
32328
+ const probeRows = app.db.select().from(discoveryProbes).where(eq32(discoveryProbes.sessionId, session.id)).all();
31659
32329
  const candidateQueries = /* @__PURE__ */ new Set();
31660
32330
  for (const probe of probeRows) {
31661
32331
  if (!probe.bucket) continue;
@@ -31663,7 +32333,7 @@ async function discoveryRoutes(app, opts) {
31663
32333
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
31664
32334
  }
31665
32335
  const existingQueries = new Set(
31666
- app.db.select({ query: queries.query }).from(queries).where(eq30(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
32336
+ app.db.select({ query: queries.query }).from(queries).where(eq32(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
31667
32337
  );
31668
32338
  const promotedQueries = [];
31669
32339
  const skippedQueries = [];
@@ -31679,7 +32349,7 @@ async function discoveryRoutes(app, opts) {
31679
32349
  const skippedCompetitors = [];
31680
32350
  if (includeCompetitors) {
31681
32351
  const existingCompetitors = new Set(
31682
- app.db.select({ domain: competitors.domain }).from(competitors).where(eq30(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
32352
+ app.db.select({ domain: competitors.domain }).from(competitors).where(eq32(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
31683
32353
  );
31684
32354
  const competitorMap = parseCompetitorMap(session.competitorMap);
31685
32355
  for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
@@ -31698,7 +32368,7 @@ async function discoveryRoutes(app, opts) {
31698
32368
  app.db.transaction((tx) => {
31699
32369
  for (const query of promotedQueries) {
31700
32370
  tx.insert(queries).values({
31701
- id: crypto25.randomUUID(),
32371
+ id: crypto26.randomUUID(),
31702
32372
  projectId: project.id,
31703
32373
  query,
31704
32374
  provenance,
@@ -31707,7 +32377,7 @@ async function discoveryRoutes(app, opts) {
31707
32377
  }
31708
32378
  for (const domain of promotedCompetitors) {
31709
32379
  tx.insert(competitors).values({
31710
- id: crypto25.randomUUID(),
32380
+ id: crypto26.randomUUID(),
31711
32381
  projectId: project.id,
31712
32382
  domain,
31713
32383
  provenance,
@@ -31782,8 +32452,8 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
31782
32452
  }
31783
32453
 
31784
32454
  // ../api-routes/src/discovery/orchestrate.ts
31785
- import crypto26 from "crypto";
31786
- import { eq as eq31 } from "drizzle-orm";
32455
+ import crypto27 from "crypto";
32456
+ import { eq as eq33 } from "drizzle-orm";
31787
32457
  var DEFAULT_MAX_PROBES = 100;
31788
32458
  var ABSOLUTE_MAX_PROBES = 500;
31789
32459
  function classifyProbeBucket(input) {
@@ -31837,7 +32507,7 @@ async function executeDiscovery(opts) {
31837
32507
  status: DiscoverySessionStatuses.seeding,
31838
32508
  dedupThreshold,
31839
32509
  startedAt
31840
- }).where(eq31(discoverySessions.id, opts.sessionId)).run();
32510
+ }).where(eq33(discoverySessions.id, opts.sessionId)).run();
31841
32511
  const seedResult = await opts.deps.seed({
31842
32512
  project: opts.project,
31843
32513
  icpDescription: opts.icpDescription,
@@ -31863,7 +32533,7 @@ async function executeDiscovery(opts) {
31863
32533
  seedCountRaw,
31864
32534
  seedCount,
31865
32535
  warning
31866
- }).where(eq31(discoverySessions.id, opts.sessionId)).run();
32536
+ }).where(eq33(discoverySessions.id, opts.sessionId)).run();
31867
32537
  const probeRows = [];
31868
32538
  const buckets = { cited: 0, aspirational: 0, "wasted-surface": 0 };
31869
32539
  for (const query of probedCanonicals) {
@@ -31876,7 +32546,7 @@ async function executeDiscovery(opts) {
31876
32546
  probeRows.push({ citedDomains: probe.citedDomains, bucket });
31877
32547
  buckets[bucket]++;
31878
32548
  opts.db.insert(discoveryProbes).values({
31879
- id: crypto26.randomUUID(),
32549
+ id: crypto27.randomUUID(),
31880
32550
  sessionId: opts.sessionId,
31881
32551
  projectId: opts.project.id,
31882
32552
  query,
@@ -31903,7 +32573,7 @@ async function executeDiscovery(opts) {
31903
32573
  wastedCount: buckets["wasted-surface"],
31904
32574
  competitorMap,
31905
32575
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
31906
- }).where(eq31(discoverySessions.id, opts.sessionId)).run();
32576
+ }).where(eq33(discoverySessions.id, opts.sessionId)).run();
31907
32577
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
31908
32578
  return {
31909
32579
  buckets,
@@ -31920,7 +32590,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
31920
32590
  const domain = normalizeDomain(entry.domain);
31921
32591
  if (!domain) continue;
31922
32592
  db.insert(domainClassifications).values({
31923
- id: crypto26.randomUUID(),
32593
+ id: crypto27.randomUUID(),
31924
32594
  projectId,
31925
32595
  domain,
31926
32596
  competitorType: entry.competitorType,
@@ -31943,7 +32613,7 @@ function markSessionFailed(db, sessionId, error) {
31943
32613
  status: DiscoverySessionStatuses.failed,
31944
32614
  error,
31945
32615
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
31946
- }).where(eq31(discoverySessions.id, sessionId)).run();
32616
+ }).where(eq33(discoverySessions.id, sessionId)).run();
31947
32617
  }
31948
32618
  function dedupeStrings(input) {
31949
32619
  const seen = /* @__PURE__ */ new Set();
@@ -31960,8 +32630,8 @@ function dedupeStrings(input) {
31960
32630
  }
31961
32631
 
31962
32632
  // ../api-routes/src/technical-aeo.ts
31963
- import crypto27 from "crypto";
31964
- import { and as and23, asc as asc3, count, desc as desc16, eq as eq32, inArray as inArray11 } from "drizzle-orm";
32633
+ import crypto28 from "crypto";
32634
+ import { and as and24, asc as asc4, count, desc as desc16, eq as eq34, inArray as inArray12 } from "drizzle-orm";
31965
32635
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
31966
32636
  function emptyScore(projectName) {
31967
32637
  return {
@@ -31993,10 +32663,10 @@ function parsePositiveInt(value, fallback, max) {
31993
32663
  async function technicalAeoRoutes(app, opts) {
31994
32664
  app.get("/projects/:name/technical-aeo", async (request) => {
31995
32665
  const project = resolveProject(app.db, request.params.name);
31996
- const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq32(siteAuditSnapshots.runId, runs.id)).where(and23(
31997
- eq32(siteAuditSnapshots.projectId, project.id),
31998
- eq32(runs.kind, RunKinds["site-audit"]),
31999
- inArray11(runs.status, SURFACEABLE_STATUSES),
32666
+ const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq34(siteAuditSnapshots.runId, runs.id)).where(and24(
32667
+ eq34(siteAuditSnapshots.projectId, project.id),
32668
+ eq34(runs.kind, RunKinds["site-audit"]),
32669
+ inArray12(runs.status, SURFACEABLE_STATUSES),
32000
32670
  notProbeRun()
32001
32671
  )).orderBy(desc16(siteAuditSnapshots.createdAt)).limit(2).all();
32002
32672
  const latest = rows[0];
@@ -32028,24 +32698,24 @@ async function technicalAeoRoutes(app, opts) {
32028
32698
  });
32029
32699
  app.get("/projects/:name/technical-aeo/pages", async (request) => {
32030
32700
  const project = resolveProject(app.db, request.params.name);
32031
- const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq32(siteAuditSnapshots.runId, runs.id)).where(and23(
32032
- eq32(siteAuditSnapshots.projectId, project.id),
32033
- eq32(runs.kind, RunKinds["site-audit"]),
32034
- inArray11(runs.status, SURFACEABLE_STATUSES),
32701
+ const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq34(siteAuditSnapshots.runId, runs.id)).where(and24(
32702
+ eq34(siteAuditSnapshots.projectId, project.id),
32703
+ eq34(runs.kind, RunKinds["site-audit"]),
32704
+ inArray12(runs.status, SURFACEABLE_STATUSES),
32035
32705
  notProbeRun()
32036
32706
  )).orderBy(desc16(siteAuditSnapshots.createdAt)).limit(1).get();
32037
32707
  if (!latest) {
32038
32708
  return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
32039
32709
  }
32040
32710
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
32041
- const conds = [eq32(siteAuditPages.runId, latest.runId)];
32042
- if (statusFilter) conds.push(eq32(siteAuditPages.status, statusFilter));
32043
- const where = and23(...conds);
32711
+ const conds = [eq34(siteAuditPages.runId, latest.runId)];
32712
+ if (statusFilter) conds.push(eq34(siteAuditPages.status, statusFilter));
32713
+ const where = and24(...conds);
32044
32714
  const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
32045
32715
  const total = totalRow?.value ?? 0;
32046
32716
  const limit = parsePositiveInt(request.query.limit, 100, 500);
32047
32717
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
32048
- const orderBy = request.query.sort === "score-desc" ? desc16(siteAuditPages.overallScore) : request.query.sort === "url" ? asc3(siteAuditPages.url) : asc3(siteAuditPages.overallScore);
32718
+ const orderBy = request.query.sort === "score-desc" ? desc16(siteAuditPages.overallScore) : request.query.sort === "url" ? asc4(siteAuditPages.url) : asc4(siteAuditPages.overallScore);
32049
32719
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
32050
32720
  const pages = rows.map((row) => ({
32051
32721
  url: row.url,
@@ -32064,10 +32734,10 @@ async function technicalAeoRoutes(app, opts) {
32064
32734
  auditedAt: siteAuditSnapshots.auditedAt,
32065
32735
  aggregateScore: siteAuditSnapshots.aggregateScore,
32066
32736
  pagesAudited: siteAuditSnapshots.pagesAudited
32067
- }).from(siteAuditSnapshots).innerJoin(runs, eq32(siteAuditSnapshots.runId, runs.id)).where(and23(
32068
- eq32(siteAuditSnapshots.projectId, project.id),
32069
- eq32(runs.kind, RunKinds["site-audit"]),
32070
- inArray11(runs.status, SURFACEABLE_STATUSES),
32737
+ }).from(siteAuditSnapshots).innerJoin(runs, eq34(siteAuditSnapshots.runId, runs.id)).where(and24(
32738
+ eq34(siteAuditSnapshots.projectId, project.id),
32739
+ eq34(runs.kind, RunKinds["site-audit"]),
32740
+ inArray12(runs.status, SURFACEABLE_STATUSES),
32071
32741
  notProbeRun()
32072
32742
  )).orderBy(desc16(siteAuditSnapshots.createdAt)).limit(limit).all();
32073
32743
  return { project: project.name, points: rows.reverse() };
@@ -32078,16 +32748,16 @@ async function technicalAeoRoutes(app, opts) {
32078
32748
  if (!parsed.success) {
32079
32749
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
32080
32750
  }
32081
- const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and23(
32082
- eq32(runs.projectId, project.id),
32083
- eq32(runs.kind, RunKinds["site-audit"]),
32084
- inArray11(runs.status, [RunStatuses.queued, RunStatuses.running])
32751
+ const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and24(
32752
+ eq34(runs.projectId, project.id),
32753
+ eq34(runs.kind, RunKinds["site-audit"]),
32754
+ inArray12(runs.status, [RunStatuses.queued, RunStatuses.running])
32085
32755
  )).get();
32086
32756
  if (existing) {
32087
32757
  return { runId: existing.id, status: existing.status };
32088
32758
  }
32089
32759
  const now = (/* @__PURE__ */ new Date()).toISOString();
32090
- const runId = crypto27.randomUUID();
32760
+ const runId = crypto28.randomUUID();
32091
32761
  app.db.insert(runs).values({
32092
32762
  id: runId,
32093
32763
  projectId: project.id,
@@ -32214,6 +32884,11 @@ async function apiRoutes(app, opts) {
32214
32884
  getTelemetryStatus: opts.getTelemetryStatus,
32215
32885
  setTelemetryEnabled: opts.setTelemetryEnabled
32216
32886
  });
32887
+ await api.register(adsRoutes, {
32888
+ adsCredentialStore: opts.adsCredentialStore,
32889
+ verifyAdsAccount: opts.verifyAdsAccount,
32890
+ onAdsSyncRequested: opts.onAdsSyncRequested
32891
+ });
32217
32892
  await api.register(bingRoutes, {
32218
32893
  bingConnectionStore: opts.bingConnectionStore,
32219
32894
  onInspectSitemapRequested: opts.onBingInspectSitemapRequested
@@ -32277,6 +32952,7 @@ async function apiRoutes(app, opts) {
32277
32952
  bingConnectionStore: opts.bingConnectionStore,
32278
32953
  wordpressConnectionStore: opts.wordpressConnectionStore,
32279
32954
  ga4CredentialStore: opts.ga4CredentialStore,
32955
+ adsCredentialStore: opts.adsCredentialStore,
32280
32956
  getGoogleAuthConfig: opts.getGoogleAuthConfig,
32281
32957
  getPlacesConfig: opts.getPlacesConfig,
32282
32958
  publicUrl: opts.publicUrl,
@@ -32427,7 +33103,7 @@ function buildTrafficSourceValidators(opts) {
32427
33103
  }
32428
33104
 
32429
33105
  // src/intelligence-service.ts
32430
- import crypto28 from "crypto";
33106
+ import crypto29 from "crypto";
32431
33107
 
32432
33108
  // src/logger.ts
32433
33109
  var IS_TTY = process.stdout.isTTY === true;
@@ -32651,6 +33327,7 @@ var IntelligenceService = class {
32651
33327
  constructor(db) {
32652
33328
  this.db = db;
32653
33329
  }
33330
+ db;
32654
33331
  /**
32655
33332
  * Analyze a completed run and persist insights + health snapshot.
32656
33333
  * Idempotent: deletes prior results for the same runId before inserting.
@@ -32658,9 +33335,9 @@ var IntelligenceService = class {
32658
33335
  */
32659
33336
  analyzeAndPersist(runId, projectId) {
32660
33337
  const recentRuns = this.db.select().from(runs).where(
32661
- and24(
32662
- eq33(runs.projectId, projectId),
32663
- or5(eq33(runs.status, "completed"), eq33(runs.status, "partial")),
33338
+ and25(
33339
+ eq35(runs.projectId, projectId),
33340
+ or5(eq35(runs.status, "completed"), eq35(runs.status, "partial")),
32664
33341
  // Defensive: RunCoordinator already skips probes before this is
32665
33342
  // called, but if a future call site invokes analyzeAndPersist
32666
33343
  // directly for a probe, probes still must not pollute the
@@ -32742,7 +33419,7 @@ var IntelligenceService = class {
32742
33419
  * Returns the persisted insights so the coordinator can count critical/high.
32743
33420
  */
32744
33421
  analyzeAndPersistGbp(runId, projectId) {
32745
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq33(runs.id, runId)).get();
33422
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq35(runs.id, runId)).get();
32746
33423
  if (!runRow) {
32747
33424
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
32748
33425
  this.persistGbpInsights(runId, projectId, [], []);
@@ -32750,11 +33427,11 @@ var IntelligenceService = class {
32750
33427
  }
32751
33428
  const windowStart = runRow.startedAt ?? runRow.createdAt;
32752
33429
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
32753
- const selected = this.db.select().from(gbpLocations).where(and24(
32754
- eq33(gbpLocations.projectId, projectId),
32755
- eq33(gbpLocations.selected, true),
32756
- gte6(gbpLocations.syncedAt, windowStart),
32757
- lte3(gbpLocations.syncedAt, windowEnd)
33430
+ const selected = this.db.select().from(gbpLocations).where(and25(
33431
+ eq35(gbpLocations.projectId, projectId),
33432
+ eq35(gbpLocations.selected, true),
33433
+ gte7(gbpLocations.syncedAt, windowStart),
33434
+ lte4(gbpLocations.syncedAt, windowEnd)
32758
33435
  )).all();
32759
33436
  if (selected.length === 0) {
32760
33437
  log.info("gbp-intelligence.skip", { runId, reason: "no locations synced during run" });
@@ -32787,10 +33464,10 @@ var IntelligenceService = class {
32787
33464
  }
32788
33465
  /** Build the per-location signal bundle the GBP analyzer consumes. */
32789
33466
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
32790
- const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and24(eq33(gbpDailyMetrics.projectId, projectId), eq33(gbpDailyMetrics.locationName, locationName))).all();
32791
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and24(eq33(gbpPlaceActions.projectId, projectId), eq33(gbpPlaceActions.locationName, locationName))).all();
32792
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and24(eq33(gbpLodgingSnapshots.projectId, projectId), eq33(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc17(gbpLodgingSnapshots.syncedAt)).limit(1).get();
32793
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and24(eq33(gbpPlaceDetails.projectId, projectId), eq33(gbpPlaceDetails.locationName, locationName))).orderBy(desc17(gbpPlaceDetails.syncedAt)).limit(1).get();
33467
+ const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and25(eq35(gbpDailyMetrics.projectId, projectId), eq35(gbpDailyMetrics.locationName, locationName))).all();
33468
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and25(eq35(gbpPlaceActions.projectId, projectId), eq35(gbpPlaceActions.locationName, locationName))).all();
33469
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and25(eq35(gbpLodgingSnapshots.projectId, projectId), eq35(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc17(gbpLodgingSnapshots.syncedAt)).limit(1).get();
33470
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and25(eq35(gbpPlaceDetails.projectId, projectId), eq35(gbpPlaceDetails.locationName, locationName))).orderBy(desc17(gbpPlaceDetails.syncedAt)).limit(1).get();
32794
33471
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
32795
33472
  const summary = buildGbpSummary({
32796
33473
  locationName,
@@ -32822,7 +33499,7 @@ var IntelligenceService = class {
32822
33499
  /** Build the month-over-month keyword series for a location from the
32823
33500
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
32824
33501
  buildGbpKeywordTrend(projectId, locationName) {
32825
- const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and24(eq33(gbpKeywordMonthly.projectId, projectId), eq33(gbpKeywordMonthly.locationName, locationName))).all();
33502
+ const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and25(eq35(gbpKeywordMonthly.projectId, projectId), eq35(gbpKeywordMonthly.locationName, locationName))).all();
32826
33503
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
32827
33504
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
32828
33505
  const recentMonth = months[0] ?? null;
@@ -32853,7 +33530,7 @@ var IntelligenceService = class {
32853
33530
  */
32854
33531
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
32855
33532
  const covered = new Set(coveredLocationNames);
32856
- const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and24(eq33(insights.projectId, projectId), eq33(insights.provider, GBP_INSIGHT_PROVIDER))).all();
33533
+ const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and25(eq35(insights.projectId, projectId), eq35(insights.provider, GBP_INSIGHT_PROVIDER))).all();
32857
33534
  const staleIds = [];
32858
33535
  const dismissedSlots = /* @__PURE__ */ new Set();
32859
33536
  for (const row of existing) {
@@ -32864,7 +33541,7 @@ var IntelligenceService = class {
32864
33541
  }
32865
33542
  this.db.transaction((tx) => {
32866
33543
  for (const id of staleIds) {
32867
- tx.delete(insights).where(eq33(insights.id, id)).run();
33544
+ tx.delete(insights).where(eq35(insights.id, id)).run();
32868
33545
  }
32869
33546
  for (const insight of gbpInsights) {
32870
33547
  const parsed = parseGbpInsightId(insight.id);
@@ -32942,7 +33619,7 @@ var IntelligenceService = class {
32942
33619
  * create per run + aggregate). DB is left untouched.
32943
33620
  */
32944
33621
  backfill(projectName, opts, onProgress) {
32945
- const project = this.db.select().from(projects).where(eq33(projects.name, projectName)).get();
33622
+ const project = this.db.select().from(projects).where(eq35(projects.name, projectName)).get();
32946
33623
  if (!project) {
32947
33624
  throw new Error(`Project "${projectName}" not found`);
32948
33625
  }
@@ -32955,13 +33632,13 @@ var IntelligenceService = class {
32955
33632
  sinceTimestamp = parsed;
32956
33633
  }
32957
33634
  const allRuns = this.db.select().from(runs).where(
32958
- and24(
32959
- eq33(runs.projectId, project.id),
32960
- or5(eq33(runs.status, "completed"), eq33(runs.status, "partial")),
33635
+ and25(
33636
+ eq35(runs.projectId, project.id),
33637
+ or5(eq35(runs.status, "completed"), eq35(runs.status, "partial")),
32961
33638
  // Backfill must not replay probe runs as if they were real sweeps.
32962
33639
  ne5(runs.trigger, RunTriggers.probe)
32963
33640
  )
32964
- ).orderBy(asc4(runs.finishedAt)).all();
33641
+ ).orderBy(asc5(runs.finishedAt)).all();
32965
33642
  let startIdx = 0;
32966
33643
  let endIdx = allRuns.length;
32967
33644
  if (opts?.fromRunId) {
@@ -32990,7 +33667,7 @@ var IntelligenceService = class {
32990
33667
  let wouldDeleteTotal = 0;
32991
33668
  const existingByRunId = /* @__PURE__ */ new Map();
32992
33669
  if (isDryRun && targetRuns.length > 0) {
32993
- const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray12(insights.runId, targetRuns.map((r) => r.id))).all();
33670
+ const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray13(insights.runId, targetRuns.map((r) => r.id))).all();
32994
33671
  for (const r of rows) {
32995
33672
  if (r.runId == null) continue;
32996
33673
  existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
@@ -33036,7 +33713,7 @@ var IntelligenceService = class {
33036
33713
  return { processed, skipped, totalInsights };
33037
33714
  }
33038
33715
  loadTrackedCompetitors(projectId) {
33039
- return this.db.select({ domain: competitors.domain }).from(competitors).where(eq33(competitors.projectId, projectId)).all().map((r) => r.domain);
33716
+ return this.db.select({ domain: competitors.domain }).from(competitors).where(eq35(competitors.projectId, projectId)).all().map((r) => r.domain);
33040
33717
  }
33041
33718
  /**
33042
33719
  * Wipe transition signals from an analysis result while keeping health.
@@ -33057,15 +33734,15 @@ var IntelligenceService = class {
33057
33734
  }
33058
33735
  persistResult(result, runId, projectId) {
33059
33736
  const previouslyDismissed = /* @__PURE__ */ new Set();
33060
- const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq33(insights.runId, runId)).all();
33737
+ const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq35(insights.runId, runId)).all();
33061
33738
  for (const row of existingInsights) {
33062
33739
  if (row.dismissed) {
33063
33740
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
33064
33741
  }
33065
33742
  }
33066
33743
  this.db.transaction((tx) => {
33067
- tx.delete(insights).where(eq33(insights.runId, runId)).run();
33068
- tx.delete(healthSnapshots).where(eq33(healthSnapshots.runId, runId)).run();
33744
+ tx.delete(insights).where(eq35(insights.runId, runId)).run();
33745
+ tx.delete(healthSnapshots).where(eq35(healthSnapshots.runId, runId)).run();
33069
33746
  const now = (/* @__PURE__ */ new Date()).toISOString();
33070
33747
  for (const insight of result.insights) {
33071
33748
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -33085,7 +33762,7 @@ var IntelligenceService = class {
33085
33762
  }).run();
33086
33763
  }
33087
33764
  tx.insert(healthSnapshots).values({
33088
- id: crypto28.randomUUID(),
33765
+ id: crypto29.randomUUID(),
33089
33766
  projectId,
33090
33767
  runId,
33091
33768
  overallCitedRate: String(result.health.overallCitedRate),
@@ -33116,24 +33793,24 @@ var IntelligenceService = class {
33116
33793
  applySeverityTiering(rawInsights, excludeRunId, projectId) {
33117
33794
  const regressions = rawInsights.filter((i) => i.type === "regression");
33118
33795
  if (regressions.length === 0) return rawInsights;
33119
- const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq33(gscSearchData.projectId, projectId)).all();
33796
+ const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq35(gscSearchData.projectId, projectId)).all();
33120
33797
  const gscConnected = gscRows.length > 0;
33121
33798
  const gscImpressionsByQuery = /* @__PURE__ */ new Map();
33122
33799
  for (const row of gscRows) {
33123
33800
  const key = row.query.toLowerCase();
33124
33801
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
33125
33802
  }
33126
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq33(projects.id, projectId)).get();
33803
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq35(projects.id, projectId)).get();
33127
33804
  const locationCount = Math.max(
33128
33805
  1,
33129
33806
  (projectRow?.locations ?? []).length
33130
33807
  );
33131
33808
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
33132
33809
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
33133
- and24(
33134
- eq33(runs.projectId, projectId),
33135
- eq33(runs.kind, RunKinds["answer-visibility"]),
33136
- or5(eq33(runs.status, "completed"), eq33(runs.status, "partial")),
33810
+ and25(
33811
+ eq35(runs.projectId, projectId),
33812
+ eq35(runs.kind, RunKinds["answer-visibility"]),
33813
+ or5(eq35(runs.status, "completed"), eq35(runs.status, "partial")),
33137
33814
  // Defensive — see top of file.
33138
33815
  ne5(runs.trigger, RunTriggers.probe)
33139
33816
  )
@@ -33153,7 +33830,7 @@ var IntelligenceService = class {
33153
33830
  const haveHistory = recentRunIds.length > 0;
33154
33831
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
33155
33832
  if (haveHistory) {
33156
- const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and24(eq33(insights.type, "regression"), inArray12(insights.runId, recentRunIds))).all();
33833
+ const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and25(eq35(insights.type, "regression"), inArray13(insights.runId, recentRunIds))).all();
33157
33834
  const regressionGroups = /* @__PURE__ */ new Map();
33158
33835
  for (const row of priorRows) {
33159
33836
  if (!row.runId) continue;
@@ -33182,7 +33859,7 @@ var IntelligenceService = class {
33182
33859
  });
33183
33860
  }
33184
33861
  buildRunData(runId, projectId, completedAt, location = null) {
33185
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq33(projects.id, projectId)).get();
33862
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq35(projects.id, projectId)).get();
33186
33863
  const projectDomains = projectDomainRow ? effectiveDomains({
33187
33864
  canonicalDomain: projectDomainRow.canonicalDomain,
33188
33865
  ownedDomains: projectDomainRow.ownedDomains
@@ -33198,7 +33875,7 @@ var IntelligenceService = class {
33198
33875
  citedDomains: querySnapshots.citedDomains,
33199
33876
  competitorOverlap: querySnapshots.competitorOverlap,
33200
33877
  snapshotLocation: querySnapshots.location
33201
- }).from(querySnapshots).leftJoin(queries, eq33(querySnapshots.queryId, queries.id)).where(eq33(querySnapshots.runId, runId)).all();
33878
+ }).from(querySnapshots).leftJoin(queries, eq35(querySnapshots.queryId, queries.id)).where(eq35(querySnapshots.runId, runId)).all();
33202
33879
  const snapshots = [];
33203
33880
  let orphanCount = 0;
33204
33881
  for (const r of rows) {
@@ -33273,6 +33950,11 @@ export {
33273
33950
  gbpPlaceActions,
33274
33951
  gbpLodgingSnapshots,
33275
33952
  gbpPlaceDetails,
33953
+ adsConnections,
33954
+ adsCampaigns,
33955
+ adsAdGroups,
33956
+ adsAds,
33957
+ adsInsightsDaily,
33276
33958
  createClient,
33277
33959
  parseJsonColumn,
33278
33960
  extractLegacyCredentials,