@ainyc/canonry 4.78.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-3WXARKUE.js → chunk-CXIGHPBE.js} +996 -324
  18. package/dist/{chunk-QKTIP6GC.js → chunk-LCABGFYN.js} +713 -286
  19. package/dist/cli.js +369 -148
  20. package/dist/index.d.ts +17 -0
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-CDVUUG7O.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;
@@ -24403,7 +24944,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
24403
24944
  const headerB64 = encode(header);
24404
24945
  const payloadB64 = encode(payload);
24405
24946
  const signingInput = `${headerB64}.${payloadB64}`;
24406
- const sign = crypto22.createSign("RSA-SHA256");
24947
+ const sign = crypto23.createSign("RSA-SHA256");
24407
24948
  sign.update(signingInput);
24408
24949
  const signature = sign.sign(privateKey, "base64url");
24409
24950
  return `${signingInput}.${signature}`;
@@ -28186,8 +28727,8 @@ async function runBackfillTask(options) {
28186
28727
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
28187
28728
  try {
28188
28729
  app.db.transaction((tx) => {
28189
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq25(runs.id, runId)).run();
28190
- 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();
28191
28732
  });
28192
28733
  } catch {
28193
28734
  }
@@ -28202,7 +28743,7 @@ async function runBackfillTask(options) {
28202
28743
  if (allEvents.length === 0) {
28203
28744
  const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
28204
28745
  try {
28205
- 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();
28206
28747
  } catch {
28207
28748
  }
28208
28749
  return;
@@ -28224,31 +28765,31 @@ async function runBackfillTask(options) {
28224
28765
  try {
28225
28766
  app.db.transaction((tx) => {
28226
28767
  tx.delete(crawlerEventsHourly).where(
28227
- and19(
28228
- eq25(crawlerEventsHourly.sourceId, sourceRow.id),
28229
- gte3(crawlerEventsHourly.tsHour, windowStartIso),
28230
- lte2(crawlerEventsHourly.tsHour, windowEndIso)
28768
+ and20(
28769
+ eq26(crawlerEventsHourly.sourceId, sourceRow.id),
28770
+ gte4(crawlerEventsHourly.tsHour, windowStartIso),
28771
+ lte3(crawlerEventsHourly.tsHour, windowEndIso)
28231
28772
  )
28232
28773
  ).run();
28233
28774
  tx.delete(aiUserFetchEventsHourly).where(
28234
- and19(
28235
- eq25(aiUserFetchEventsHourly.sourceId, sourceRow.id),
28236
- gte3(aiUserFetchEventsHourly.tsHour, windowStartIso),
28237
- lte2(aiUserFetchEventsHourly.tsHour, windowEndIso)
28775
+ and20(
28776
+ eq26(aiUserFetchEventsHourly.sourceId, sourceRow.id),
28777
+ gte4(aiUserFetchEventsHourly.tsHour, windowStartIso),
28778
+ lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
28238
28779
  )
28239
28780
  ).run();
28240
28781
  tx.delete(aiReferralEventsHourly).where(
28241
- and19(
28242
- eq25(aiReferralEventsHourly.sourceId, sourceRow.id),
28243
- gte3(aiReferralEventsHourly.tsHour, windowStartIso),
28244
- lte2(aiReferralEventsHourly.tsHour, windowEndIso)
28782
+ and20(
28783
+ eq26(aiReferralEventsHourly.sourceId, sourceRow.id),
28784
+ gte4(aiReferralEventsHourly.tsHour, windowStartIso),
28785
+ lte3(aiReferralEventsHourly.tsHour, windowEndIso)
28245
28786
  )
28246
28787
  ).run();
28247
28788
  tx.delete(rawEventSamples).where(
28248
- and19(
28249
- eq25(rawEventSamples.sourceId, sourceRow.id),
28250
- gte3(rawEventSamples.ts, windowStartIso),
28251
- lte2(rawEventSamples.ts, windowEndIso)
28789
+ and20(
28790
+ eq26(rawEventSamples.sourceId, sourceRow.id),
28791
+ gte4(rawEventSamples.ts, windowStartIso),
28792
+ lte3(rawEventSamples.ts, windowEndIso)
28252
28793
  )
28253
28794
  ).run();
28254
28795
  for (const bucket of report.crawlerEventsHourly) {
@@ -28311,7 +28852,7 @@ async function runBackfillTask(options) {
28311
28852
  }
28312
28853
  })();
28313
28854
  tx.insert(rawEventSamples).values({
28314
- id: crypto23.randomUUID(),
28855
+ id: crypto24.randomUUID(),
28315
28856
  projectId: project.id,
28316
28857
  sourceId: sourceRow.id,
28317
28858
  ts: sample.observedAt,
@@ -28335,8 +28876,8 @@ async function runBackfillTask(options) {
28335
28876
  lastError: null,
28336
28877
  lastEventIds: newRingBuffer,
28337
28878
  updatedAt: finishedAt
28338
- }).where(eq25(trafficSources.id, sourceRow.id)).run();
28339
- 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();
28340
28881
  });
28341
28882
  } catch (e) {
28342
28883
  markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -28418,7 +28959,7 @@ async function trafficRoutes(app, opts) {
28418
28959
  createdAt: existing?.createdAt ?? now,
28419
28960
  updatedAt: now
28420
28961
  });
28421
- 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);
28422
28963
  const config = {
28423
28964
  gcpProjectId,
28424
28965
  serviceName: serviceName ?? null,
@@ -28434,10 +28975,10 @@ async function trafficRoutes(app, opts) {
28434
28975
  lastError: null,
28435
28976
  configJson: config,
28436
28977
  updatedAt: now
28437
- }).where(eq25(trafficSources.id, activeSource.id)).run();
28438
- 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();
28439
28980
  } else {
28440
- const newId = crypto23.randomUUID();
28981
+ const newId = crypto24.randomUUID();
28441
28982
  app.db.insert(trafficSources).values({
28442
28983
  id: newId,
28443
28984
  projectId: project.id,
@@ -28452,7 +28993,7 @@ async function trafficRoutes(app, opts) {
28452
28993
  createdAt: now,
28453
28994
  updatedAt: now
28454
28995
  }).run();
28455
- 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();
28456
28997
  }
28457
28998
  writeAuditLog(app.db, {
28458
28999
  projectId: project.id,
@@ -28504,7 +29045,7 @@ async function trafficRoutes(app, opts) {
28504
29045
  createdAt: existing?.createdAt ?? now,
28505
29046
  updatedAt: now
28506
29047
  });
28507
- 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);
28508
29049
  const config = { baseUrl, username };
28509
29050
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
28510
29051
  let sourceRow;
@@ -28515,10 +29056,10 @@ async function trafficRoutes(app, opts) {
28515
29056
  lastError: null,
28516
29057
  configJson: config,
28517
29058
  updatedAt: now
28518
- }).where(eq25(trafficSources.id, activeSource.id)).run();
28519
- 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();
28520
29061
  } else {
28521
- const newId = crypto23.randomUUID();
29062
+ const newId = crypto24.randomUUID();
28522
29063
  app.db.insert(trafficSources).values({
28523
29064
  id: newId,
28524
29065
  projectId: project.id,
@@ -28533,7 +29074,7 @@ async function trafficRoutes(app, opts) {
28533
29074
  createdAt: now,
28534
29075
  updatedAt: now
28535
29076
  }).run();
28536
- 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();
28537
29078
  }
28538
29079
  writeAuditLog(app.db, {
28539
29080
  projectId: project.id,
@@ -28587,7 +29128,7 @@ async function trafficRoutes(app, opts) {
28587
29128
  createdAt: existing?.createdAt ?? now,
28588
29129
  updatedAt: now
28589
29130
  });
28590
- 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);
28591
29132
  const config = { projectId, teamId, environment };
28592
29133
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
28593
29134
  const { sourceRow, scheduleCreated } = app.db.transaction((tx) => {
@@ -28599,10 +29140,10 @@ async function trafficRoutes(app, opts) {
28599
29140
  lastError: null,
28600
29141
  configJson: config,
28601
29142
  updatedAt: now
28602
- }).where(eq25(trafficSources.id, activeSource.id)).run();
28603
- 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();
28604
29145
  } else {
28605
- const newId = crypto23.randomUUID();
29146
+ const newId = crypto24.randomUUID();
28606
29147
  tx.insert(trafficSources).values({
28607
29148
  id: newId,
28608
29149
  projectId: project.id,
@@ -28625,18 +29166,18 @@ async function trafficRoutes(app, opts) {
28625
29166
  createdAt: now,
28626
29167
  updatedAt: now
28627
29168
  }).run();
28628
- row = tx.select().from(trafficSources).where(eq25(trafficSources.id, newId)).get();
29169
+ row = tx.select().from(trafficSources).where(eq26(trafficSources.id, newId)).get();
28629
29170
  }
28630
29171
  const existingSchedule = tx.select().from(schedules).where(
28631
- and19(
28632
- eq25(schedules.projectId, project.id),
28633
- eq25(schedules.kind, SchedulableRunKinds["traffic-sync"])
29172
+ and20(
29173
+ eq26(schedules.projectId, project.id),
29174
+ eq26(schedules.kind, SchedulableRunKinds["traffic-sync"])
28634
29175
  )
28635
29176
  ).get();
28636
29177
  let created = false;
28637
29178
  if (!existingSchedule) {
28638
29179
  tx.insert(schedules).values({
28639
- id: crypto23.randomUUID(),
29180
+ id: crypto24.randomUUID(),
28640
29181
  projectId: project.id,
28641
29182
  kind: SchedulableRunKinds["traffic-sync"],
28642
29183
  cronExpr: DEFAULT_TRAFFIC_SYNC_CRON,
@@ -28679,7 +29220,7 @@ async function trafficRoutes(app, opts) {
28679
29220
  });
28680
29221
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
28681
29222
  const project = resolveProject(app.db, request.params.name);
28682
- 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();
28683
29224
  if (!sourceRow || sourceRow.projectId !== project.id) {
28684
29225
  throw notFound("Traffic source", request.params.id);
28685
29226
  }
@@ -28691,7 +29232,7 @@ async function trafficRoutes(app, opts) {
28691
29232
  const windowEnd = /* @__PURE__ */ new Date();
28692
29233
  const startedAt = windowEnd.toISOString();
28693
29234
  const syncStartedAtMs = windowEnd.getTime();
28694
- const runId = crypto23.randomUUID();
29235
+ const runId = crypto24.randomUUID();
28695
29236
  app.db.insert(runs).values({
28696
29237
  id: runId,
28697
29238
  projectId: project.id,
@@ -28705,8 +29246,8 @@ async function trafficRoutes(app, opts) {
28705
29246
  const markFailed = (msg, errorCode) => {
28706
29247
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
28707
29248
  app.db.transaction((tx) => {
28708
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq25(runs.id, runId)).run();
28709
- 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();
28710
29251
  });
28711
29252
  try {
28712
29253
  opts.onTrafficSynced?.({
@@ -28787,7 +29328,7 @@ async function trafficRoutes(app, opts) {
28787
29328
  }
28788
29329
  const credential = credentialStore.getConnection(project.name);
28789
29330
  if (!credential) {
28790
- app.db.delete(runs).where(eq25(runs.id, runId)).run();
29331
+ app.db.delete(runs).where(eq26(runs.id, runId)).run();
28791
29332
  throw validationError(
28792
29333
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
28793
29334
  );
@@ -28836,12 +29377,12 @@ async function trafficRoutes(app, opts) {
28836
29377
  auditAction = "traffic.vercel.synced";
28837
29378
  const credentialStore = opts.vercelTrafficCredentialStore;
28838
29379
  if (!credentialStore) {
28839
- app.db.delete(runs).where(eq25(runs.id, runId)).run();
29380
+ app.db.delete(runs).where(eq26(runs.id, runId)).run();
28840
29381
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
28841
29382
  }
28842
29383
  const credential = credentialStore.getConnection(project.name);
28843
29384
  if (!credential) {
28844
- app.db.delete(runs).where(eq25(runs.id, runId)).run();
29385
+ app.db.delete(runs).where(eq26(runs.id, runId)).run();
28845
29386
  throw validationError(
28846
29387
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
28847
29388
  );
@@ -28941,7 +29482,7 @@ async function trafficRoutes(app, opts) {
28941
29482
  let aiReferralHitsCount = 0;
28942
29483
  let unknownHitsCount = 0;
28943
29484
  app.db.transaction((tx) => {
28944
- 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();
28945
29486
  const previousIds = latestRow.lastEventIds ?? [];
28946
29487
  const seenEventIds = new Set(previousIds);
28947
29488
  const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
@@ -29074,7 +29615,7 @@ async function trafficRoutes(app, opts) {
29074
29615
  }
29075
29616
  })();
29076
29617
  tx.insert(rawEventSamples).values({
29077
- id: crypto23.randomUUID(),
29618
+ id: crypto24.randomUUID(),
29078
29619
  projectId: project.id,
29079
29620
  sourceId: sourceRow.id,
29080
29621
  ts: sample.observedAt,
@@ -29109,8 +29650,8 @@ async function trafficRoutes(app, opts) {
29109
29650
  if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
29110
29651
  sourceUpdate.lastCursor = nextCursor ?? null;
29111
29652
  }
29112
- tx.update(trafficSources).set(sourceUpdate).where(eq25(trafficSources.id, sourceRow.id)).run();
29113
- 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();
29114
29655
  });
29115
29656
  writeAuditLog(app.db, {
29116
29657
  projectId: project.id,
@@ -29162,7 +29703,7 @@ async function trafficRoutes(app, opts) {
29162
29703
  });
29163
29704
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
29164
29705
  const project = resolveProject(app.db, request.params.name);
29165
- 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();
29166
29707
  if (!sourceRow || sourceRow.projectId !== project.id) {
29167
29708
  throw notFound("Traffic source", request.params.id);
29168
29709
  }
@@ -29312,7 +29853,7 @@ async function trafficRoutes(app, opts) {
29312
29853
  };
29313
29854
  }
29314
29855
  const startedAt = windowEnd.toISOString();
29315
- const runId = crypto23.randomUUID();
29856
+ const runId = crypto24.randomUUID();
29316
29857
  app.db.insert(runs).values({
29317
29858
  id: runId,
29318
29859
  projectId: project.id,
@@ -29347,34 +29888,34 @@ async function trafficRoutes(app, opts) {
29347
29888
  });
29348
29889
  function buildSourceDetail(projectId, row, since) {
29349
29890
  const crawlerTotals = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
29350
- and19(
29351
- eq25(crawlerEventsHourly.sourceId, row.id),
29352
- gte3(crawlerEventsHourly.tsHour, since)
29891
+ and20(
29892
+ eq26(crawlerEventsHourly.sourceId, row.id),
29893
+ gte4(crawlerEventsHourly.tsHour, since)
29353
29894
  )
29354
29895
  ).get();
29355
29896
  const aiUserFetchTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
29356
- and19(
29357
- eq25(aiUserFetchEventsHourly.sourceId, row.id),
29358
- gte3(aiUserFetchEventsHourly.tsHour, since)
29897
+ and20(
29898
+ eq26(aiUserFetchEventsHourly.sourceId, row.id),
29899
+ gte4(aiUserFetchEventsHourly.tsHour, since)
29359
29900
  )
29360
29901
  ).get();
29361
29902
  const aiTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
29362
- and19(
29363
- eq25(aiReferralEventsHourly.sourceId, row.id),
29364
- gte3(aiReferralEventsHourly.tsHour, since)
29903
+ and20(
29904
+ eq26(aiReferralEventsHourly.sourceId, row.id),
29905
+ gte4(aiReferralEventsHourly.tsHour, since)
29365
29906
  )
29366
29907
  ).get();
29367
29908
  const sampleTotals = app.db.select({ total: sql11`COUNT(*)` }).from(rawEventSamples).where(
29368
- and19(
29369
- eq25(rawEventSamples.sourceId, row.id),
29370
- gte3(rawEventSamples.ts, since)
29909
+ and20(
29910
+ eq26(rawEventSamples.sourceId, row.id),
29911
+ gte4(rawEventSamples.ts, since)
29371
29912
  )
29372
29913
  ).get();
29373
29914
  const latestRun = app.db.select().from(runs).where(
29374
- and19(
29375
- eq25(runs.projectId, projectId),
29376
- eq25(runs.kind, RunKinds["traffic-sync"]),
29377
- 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)
29378
29919
  )
29379
29920
  ).orderBy(desc14(runs.startedAt)).limit(1).get();
29380
29921
  return {
@@ -29402,7 +29943,7 @@ async function trafficRoutes(app, opts) {
29402
29943
  "`advanceToNow` must be `true`. There is no implicit reset."
29403
29944
  );
29404
29945
  }
29405
- 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();
29406
29947
  if (!sourceRow) {
29407
29948
  throw notFound("traffic source", request.params.id);
29408
29949
  }
@@ -29419,7 +29960,7 @@ async function trafficRoutes(app, opts) {
29419
29960
  status: TrafficSourceStatuses.connected,
29420
29961
  lastError: null,
29421
29962
  updatedAt: now
29422
- }).where(eq25(trafficSources.id, sourceRow.id)).run();
29963
+ }).where(eq26(trafficSources.id, sourceRow.id)).run();
29423
29964
  writeAuditLog(tx, auditFromRequest(request, {
29424
29965
  projectId: project.id,
29425
29966
  actor: "api",
@@ -29427,20 +29968,20 @@ async function trafficRoutes(app, opts) {
29427
29968
  entityType: "traffic_source",
29428
29969
  entityId: sourceRow.id
29429
29970
  }));
29430
- 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();
29431
29972
  });
29432
29973
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
29433
29974
  });
29434
29975
  app.get("/projects/:name/traffic/sources", async (request) => {
29435
29976
  const project = resolveProject(app.db, request.params.name);
29436
- 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();
29437
29978
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
29438
29979
  const response = { sources };
29439
29980
  return response;
29440
29981
  });
29441
29982
  app.get("/projects/:name/traffic/status", async (request) => {
29442
29983
  const project = resolveProject(app.db, request.params.name);
29443
- 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();
29444
29985
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
29445
29986
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
29446
29987
  const response = { sources };
@@ -29450,7 +29991,7 @@ async function trafficRoutes(app, opts) {
29450
29991
  "/projects/:name/traffic/sources/:id",
29451
29992
  async (request) => {
29452
29993
  const project = resolveProject(app.db, request.params.name);
29453
- 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();
29454
29995
  if (!row || row.projectId !== project.id) {
29455
29996
  throw notFound("Traffic source", request.params.id);
29456
29997
  }
@@ -29501,12 +30042,12 @@ async function trafficRoutes(app, opts) {
29501
30042
  let aiReferralTotal = 0;
29502
30043
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
29503
30044
  const crawlerFilters = [
29504
- eq25(crawlerEventsHourly.projectId, project.id),
29505
- gte3(crawlerEventsHourly.tsHour, sinceIso),
29506
- lte2(crawlerEventsHourly.tsHour, untilIso)
30045
+ eq26(crawlerEventsHourly.projectId, project.id),
30046
+ gte4(crawlerEventsHourly.tsHour, sinceIso),
30047
+ lte3(crawlerEventsHourly.tsHour, untilIso)
29507
30048
  ];
29508
- if (sourceIdParam) crawlerFilters.push(eq25(crawlerEventsHourly.sourceId, sourceIdParam));
29509
- const crawlerWhere = and19(...crawlerFilters);
30049
+ if (sourceIdParam) crawlerFilters.push(eq26(crawlerEventsHourly.sourceId, sourceIdParam));
30050
+ const crawlerWhere = and20(...crawlerFilters);
29510
30051
  const total = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
29511
30052
  crawlerTotal = Number(total?.total ?? 0);
29512
30053
  const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc14(crawlerEventsHourly.tsHour)).limit(limit).all();
@@ -29526,12 +30067,12 @@ async function trafficRoutes(app, opts) {
29526
30067
  }
29527
30068
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
29528
30069
  const userFetchFilters = [
29529
- eq25(aiUserFetchEventsHourly.projectId, project.id),
29530
- gte3(aiUserFetchEventsHourly.tsHour, sinceIso),
29531
- lte2(aiUserFetchEventsHourly.tsHour, untilIso)
30070
+ eq26(aiUserFetchEventsHourly.projectId, project.id),
30071
+ gte4(aiUserFetchEventsHourly.tsHour, sinceIso),
30072
+ lte3(aiUserFetchEventsHourly.tsHour, untilIso)
29532
30073
  ];
29533
- if (sourceIdParam) userFetchFilters.push(eq25(aiUserFetchEventsHourly.sourceId, sourceIdParam));
29534
- const userFetchWhere = and19(...userFetchFilters);
30074
+ if (sourceIdParam) userFetchFilters.push(eq26(aiUserFetchEventsHourly.sourceId, sourceIdParam));
30075
+ const userFetchWhere = and20(...userFetchFilters);
29535
30076
  const total = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
29536
30077
  aiUserFetchTotal = Number(total?.total ?? 0);
29537
30078
  const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc14(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
@@ -29551,12 +30092,12 @@ async function trafficRoutes(app, opts) {
29551
30092
  }
29552
30093
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
29553
30094
  const aiFilters = [
29554
- eq25(aiReferralEventsHourly.projectId, project.id),
29555
- gte3(aiReferralEventsHourly.tsHour, sinceIso),
29556
- lte2(aiReferralEventsHourly.tsHour, untilIso)
30095
+ eq26(aiReferralEventsHourly.projectId, project.id),
30096
+ gte4(aiReferralEventsHourly.tsHour, sinceIso),
30097
+ lte3(aiReferralEventsHourly.tsHour, untilIso)
29557
30098
  ];
29558
- if (sourceIdParam) aiFilters.push(eq25(aiReferralEventsHourly.sourceId, sourceIdParam));
29559
- const aiWhere = and19(...aiFilters);
30099
+ if (sourceIdParam) aiFilters.push(eq26(aiReferralEventsHourly.sourceId, sourceIdParam));
30100
+ const aiWhere = and20(...aiFilters);
29560
30101
  const total = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
29561
30102
  aiReferralTotal = Number(total?.total ?? 0);
29562
30103
  const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc14(aiReferralEventsHourly.tsHour)).limit(limit).all();
@@ -29592,7 +30133,7 @@ async function trafficRoutes(app, opts) {
29592
30133
  }
29593
30134
 
29594
30135
  // ../api-routes/src/doctor/checks/agent.ts
29595
- import crypto24 from "crypto";
30136
+ import crypto25 from "crypto";
29596
30137
  import fs6 from "fs";
29597
30138
  import path7 from "path";
29598
30139
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -29745,7 +30286,7 @@ function isInstalled(dir) {
29745
30286
  }
29746
30287
  function hashInstalledFile(filePath) {
29747
30288
  try {
29748
- return crypto24.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
30289
+ return crypto25.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
29749
30290
  } catch {
29750
30291
  return void 0;
29751
30292
  }
@@ -29906,7 +30447,7 @@ var BING_AUTH_CHECKS = [
29906
30447
  ];
29907
30448
 
29908
30449
  // ../api-routes/src/doctor/checks/content.ts
29909
- import { eq as eq26 } from "drizzle-orm";
30450
+ import { eq as eq27 } from "drizzle-orm";
29910
30451
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
29911
30452
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
29912
30453
  function skippedNoProject() {
@@ -29919,7 +30460,7 @@ function skippedNoProject() {
29919
30460
  }
29920
30461
  function loadProject(ctx) {
29921
30462
  if (!ctx.project) return null;
29922
- 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;
29923
30464
  }
29924
30465
  function percent(value) {
29925
30466
  return Math.round(value * 100);
@@ -30010,6 +30551,123 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
30010
30551
  CONTENT_CHECKS.map((check) => [check.id, check])
30011
30552
  );
30012
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
+
30013
30671
  // ../api-routes/src/doctor/checks/ga-auth.ts
30014
30672
  async function checkServiceAccount(conn) {
30015
30673
  if (!conn.propertyId) {
@@ -30152,9 +30810,9 @@ var ga4ConnectionCheck = {
30152
30810
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
30153
30811
 
30154
30812
  // ../api-routes/src/doctor/checks/gbp-auth.ts
30155
- import { and as and20, eq as eq27 } from "drizzle-orm";
30156
- var RECENT_SYNC_WARN_DAYS = 7;
30157
- 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;
30158
30816
  function skippedNoProject2() {
30159
30817
  return {
30160
30818
  status: CheckStatuses.skipped,
@@ -30385,7 +31043,7 @@ var recentSyncCheck = {
30385
31043
  title: "GBP recent sync",
30386
31044
  run: (ctx) => {
30387
31045
  if (!ctx.project) return skippedNoProject2();
30388
- 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();
30389
31047
  if (selected.length === 0) {
30390
31048
  return {
30391
31049
  status: CheckStatuses.skipped,
@@ -30407,20 +31065,20 @@ var recentSyncCheck = {
30407
31065
  const newest = Math.max(...syncTimes);
30408
31066
  const ageDays = (Date.now() - newest) / (1e3 * 60 * 60 * 24);
30409
31067
  const details = { selectedLocations: selected.length, newestSyncAgeDays: Math.round(ageDays) };
30410
- if (ageDays > RECENT_SYNC_FAIL_DAYS) {
31068
+ if (ageDays > RECENT_SYNC_FAIL_DAYS2) {
30411
31069
  return {
30412
31070
  status: CheckStatuses.fail,
30413
31071
  code: "gbp.data.stale",
30414
- 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).`,
30415
31073
  remediation: `Run \`canonry gbp sync ${ctx.project.name}\` or set a gbp-sync schedule.`,
30416
31074
  details
30417
31075
  };
30418
31076
  }
30419
- if (ageDays > RECENT_SYNC_WARN_DAYS) {
31077
+ if (ageDays > RECENT_SYNC_WARN_DAYS2) {
30420
31078
  return {
30421
31079
  status: CheckStatuses.warn,
30422
31080
  code: "gbp.data.aging",
30423
- 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).`,
30424
31082
  remediation: `Run \`canonry gbp sync ${ctx.project.name}\` or set a gbp-sync schedule to keep data fresh.`,
30425
31083
  details
30426
31084
  };
@@ -30445,7 +31103,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
30445
31103
  );
30446
31104
 
30447
31105
  // ../api-routes/src/doctor/checks/places.ts
30448
- import { eq as eq28 } from "drizzle-orm";
31106
+ import { eq as eq30 } from "drizzle-orm";
30449
31107
  var apiKeyCheck = {
30450
31108
  id: "gbp.places.api-key",
30451
31109
  category: CheckCategories.auth,
@@ -30490,7 +31148,7 @@ var apiKeyCheck = {
30490
31148
  details: { tier: cfg.tier }
30491
31149
  };
30492
31150
  }
30493
- 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();
30494
31152
  const selected = rows.filter((r) => r.selected);
30495
31153
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
30496
31154
  const details = {
@@ -30941,7 +31599,7 @@ var RUNTIME_STATE_CHECKS = [
30941
31599
  ];
30942
31600
 
30943
31601
  // ../api-routes/src/doctor/checks/traffic-source.ts
30944
- 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";
30945
31603
  var RECENT_DATA_WARN_DAYS = 7;
30946
31604
  var RECENT_DATA_FAIL_DAYS = 30;
30947
31605
  function skippedNoProject4() {
@@ -30955,8 +31613,8 @@ function skippedNoProject4() {
30955
31613
  function loadProbes(ctx) {
30956
31614
  if (!ctx.project) return [];
30957
31615
  const rows = ctx.db.select().from(trafficSources).where(
30958
- and21(
30959
- eq29(trafficSources.projectId, ctx.project.id),
31616
+ and22(
31617
+ eq31(trafficSources.projectId, ctx.project.id),
30960
31618
  ne4(trafficSources.status, TrafficSourceStatuses.archived)
30961
31619
  )
30962
31620
  ).all();
@@ -31036,17 +31694,17 @@ var recentDataCheck = {
31036
31694
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
31037
31695
  const recentCrawlers = Number(
31038
31696
  ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
31039
- and21(
31040
- eq29(crawlerEventsHourly.projectId, ctx.project.id),
31041
- gte4(crawlerEventsHourly.tsHour, warnCutoff)
31697
+ and22(
31698
+ eq31(crawlerEventsHourly.projectId, ctx.project.id),
31699
+ gte5(crawlerEventsHourly.tsHour, warnCutoff)
31042
31700
  )
31043
31701
  ).get()?.total ?? 0
31044
31702
  );
31045
31703
  const recentReferrals = Number(
31046
31704
  ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
31047
- and21(
31048
- eq29(aiReferralEventsHourly.projectId, ctx.project.id),
31049
- gte4(aiReferralEventsHourly.tsHour, warnCutoff)
31705
+ and22(
31706
+ eq31(aiReferralEventsHourly.projectId, ctx.project.id),
31707
+ gte5(aiReferralEventsHourly.tsHour, warnCutoff)
31050
31708
  )
31051
31709
  ).get()?.total ?? 0
31052
31710
  );
@@ -31060,17 +31718,17 @@ var recentDataCheck = {
31060
31718
  }
31061
31719
  const olderCrawlers = Number(
31062
31720
  ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
31063
- and21(
31064
- eq29(crawlerEventsHourly.projectId, ctx.project.id),
31065
- gte4(crawlerEventsHourly.tsHour, failCutoff)
31721
+ and22(
31722
+ eq31(crawlerEventsHourly.projectId, ctx.project.id),
31723
+ gte5(crawlerEventsHourly.tsHour, failCutoff)
31066
31724
  )
31067
31725
  ).get()?.total ?? 0
31068
31726
  );
31069
31727
  const olderReferrals = Number(
31070
31728
  ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
31071
- and21(
31072
- eq29(aiReferralEventsHourly.projectId, ctx.project.id),
31073
- gte4(aiReferralEventsHourly.tsHour, failCutoff)
31729
+ and22(
31730
+ eq31(aiReferralEventsHourly.projectId, ctx.project.id),
31731
+ gte5(aiReferralEventsHourly.tsHour, failCutoff)
31074
31732
  )
31075
31733
  ).get()?.total ?? 0
31076
31734
  );
@@ -31354,6 +32012,7 @@ var ALL_CHECKS = [
31354
32012
  ...BING_AUTH_CHECKS,
31355
32013
  ...WORDPRESS_PUBLISH_CHECKS,
31356
32014
  ...GA_AUTH_CHECKS,
32015
+ ...ADS_CHECKS,
31357
32016
  ...PROVIDERS_CHECKS,
31358
32017
  ...TRAFFIC_SOURCE_CHECKS,
31359
32018
  ...CONTENT_CHECKS,
@@ -31440,6 +32099,7 @@ async function doctorRoutes(app, opts) {
31440
32099
  bingConnectionStore: opts.bingConnectionStore,
31441
32100
  wordpressConnectionStore: opts.wordpressConnectionStore,
31442
32101
  ga4CredentialStore: opts.ga4CredentialStore,
32102
+ adsCredentialStore: opts.adsCredentialStore,
31443
32103
  getGoogleAuthConfig: opts.getGoogleAuthConfig,
31444
32104
  getPlacesConfig: opts.getPlacesConfig,
31445
32105
  redirectUri,
@@ -31465,6 +32125,7 @@ async function doctorRoutes(app, opts) {
31465
32125
  bingConnectionStore: opts.bingConnectionStore,
31466
32126
  wordpressConnectionStore: opts.wordpressConnectionStore,
31467
32127
  ga4CredentialStore: opts.ga4CredentialStore,
32128
+ adsCredentialStore: opts.adsCredentialStore,
31468
32129
  getGoogleAuthConfig: opts.getGoogleAuthConfig,
31469
32130
  getPlacesConfig: opts.getPlacesConfig,
31470
32131
  redirectUri,
@@ -31478,8 +32139,8 @@ async function doctorRoutes(app, opts) {
31478
32139
  }
31479
32140
 
31480
32141
  // ../api-routes/src/discovery/routes.ts
31481
- import crypto25 from "crypto";
31482
- 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";
31483
32144
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
31484
32145
  async function discoveryRoutes(app, opts) {
31485
32146
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -31511,21 +32172,21 @@ async function discoveryRoutes(app, opts) {
31511
32172
  const now = (/* @__PURE__ */ new Date()).toISOString();
31512
32173
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
31513
32174
  const decision = app.db.transaction((tx) => {
31514
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and22(
31515
- eq30(discoverySessions.projectId, project.id),
31516
- eq30(discoverySessions.icpDescription, icpDescription),
31517
- 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, [
31518
32179
  DiscoverySessionStatuses.queued,
31519
32180
  DiscoverySessionStatuses.seeding,
31520
32181
  DiscoverySessionStatuses.probing
31521
32182
  ]),
31522
- gte5(discoverySessions.createdAt, ageFloorIso)
32183
+ gte6(discoverySessions.createdAt, ageFloorIso)
31523
32184
  )).orderBy(desc15(discoverySessions.createdAt)).get();
31524
32185
  if (existing && existing.runId) {
31525
32186
  return { reused: true, sessionId: existing.id, runId: existing.runId };
31526
32187
  }
31527
- const sessionId = crypto25.randomUUID();
31528
- const runId = crypto25.randomUUID();
32188
+ const sessionId = crypto26.randomUUID();
32189
+ const runId = crypto26.randomUUID();
31529
32190
  tx.insert(discoverySessions).values({
31530
32191
  id: sessionId,
31531
32192
  projectId: project.id,
@@ -31583,7 +32244,7 @@ async function discoveryRoutes(app, opts) {
31583
32244
  const project = resolveProject(app.db, request.params.name);
31584
32245
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
31585
32246
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
31586
- 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();
31587
32248
  return reply.send(rows.map(serializeSession));
31588
32249
  }
31589
32250
  );
@@ -31591,11 +32252,11 @@ async function discoveryRoutes(app, opts) {
31591
32252
  "/projects/:name/discover/sessions/:id",
31592
32253
  async (request, reply) => {
31593
32254
  const project = resolveProject(app.db, request.params.name);
31594
- 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();
31595
32256
  if (!session || session.projectId !== project.id) {
31596
32257
  throw notFound("Discovery session", request.params.id);
31597
32258
  }
31598
- 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();
31599
32260
  const detail = {
31600
32261
  ...serializeSession(session),
31601
32262
  probes: probeRows.map(serializeProbe)
@@ -31607,12 +32268,12 @@ async function discoveryRoutes(app, opts) {
31607
32268
  "/projects/:name/discover/sessions/:id/promote",
31608
32269
  async (request, reply) => {
31609
32270
  const project = resolveProject(app.db, request.params.name);
31610
- 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();
31611
32272
  if (!session || session.projectId !== project.id) {
31612
32273
  throw notFound("Discovery session", request.params.id);
31613
32274
  }
31614
- const probeRows = app.db.select().from(discoveryProbes).where(eq30(discoveryProbes.sessionId, session.id)).all();
31615
- 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());
31616
32277
  const seenCompetitors = new Set(existingCompetitors);
31617
32278
  const cited = /* @__PURE__ */ new Set();
31618
32279
  const aspirational = /* @__PURE__ */ new Set();
@@ -31641,7 +32302,7 @@ async function discoveryRoutes(app, opts) {
31641
32302
  );
31642
32303
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
31643
32304
  const project = resolveProject(app.db, request.params.name);
31644
- 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();
31645
32306
  if (!session || session.projectId !== project.id) {
31646
32307
  throw notFound("Discovery session", request.params.id);
31647
32308
  }
@@ -31664,7 +32325,7 @@ async function discoveryRoutes(app, opts) {
31664
32325
  const bucketSet = new Set(buckets);
31665
32326
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
31666
32327
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
31667
- 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();
31668
32329
  const candidateQueries = /* @__PURE__ */ new Set();
31669
32330
  for (const probe of probeRows) {
31670
32331
  if (!probe.bucket) continue;
@@ -31672,7 +32333,7 @@ async function discoveryRoutes(app, opts) {
31672
32333
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
31673
32334
  }
31674
32335
  const existingQueries = new Set(
31675
- 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())
31676
32337
  );
31677
32338
  const promotedQueries = [];
31678
32339
  const skippedQueries = [];
@@ -31688,7 +32349,7 @@ async function discoveryRoutes(app, opts) {
31688
32349
  const skippedCompetitors = [];
31689
32350
  if (includeCompetitors) {
31690
32351
  const existingCompetitors = new Set(
31691
- 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())
31692
32353
  );
31693
32354
  const competitorMap = parseCompetitorMap(session.competitorMap);
31694
32355
  for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
@@ -31707,7 +32368,7 @@ async function discoveryRoutes(app, opts) {
31707
32368
  app.db.transaction((tx) => {
31708
32369
  for (const query of promotedQueries) {
31709
32370
  tx.insert(queries).values({
31710
- id: crypto25.randomUUID(),
32371
+ id: crypto26.randomUUID(),
31711
32372
  projectId: project.id,
31712
32373
  query,
31713
32374
  provenance,
@@ -31716,7 +32377,7 @@ async function discoveryRoutes(app, opts) {
31716
32377
  }
31717
32378
  for (const domain of promotedCompetitors) {
31718
32379
  tx.insert(competitors).values({
31719
- id: crypto25.randomUUID(),
32380
+ id: crypto26.randomUUID(),
31720
32381
  projectId: project.id,
31721
32382
  domain,
31722
32383
  provenance,
@@ -31791,8 +32452,8 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
31791
32452
  }
31792
32453
 
31793
32454
  // ../api-routes/src/discovery/orchestrate.ts
31794
- import crypto26 from "crypto";
31795
- import { eq as eq31 } from "drizzle-orm";
32455
+ import crypto27 from "crypto";
32456
+ import { eq as eq33 } from "drizzle-orm";
31796
32457
  var DEFAULT_MAX_PROBES = 100;
31797
32458
  var ABSOLUTE_MAX_PROBES = 500;
31798
32459
  function classifyProbeBucket(input) {
@@ -31846,7 +32507,7 @@ async function executeDiscovery(opts) {
31846
32507
  status: DiscoverySessionStatuses.seeding,
31847
32508
  dedupThreshold,
31848
32509
  startedAt
31849
- }).where(eq31(discoverySessions.id, opts.sessionId)).run();
32510
+ }).where(eq33(discoverySessions.id, opts.sessionId)).run();
31850
32511
  const seedResult = await opts.deps.seed({
31851
32512
  project: opts.project,
31852
32513
  icpDescription: opts.icpDescription,
@@ -31872,7 +32533,7 @@ async function executeDiscovery(opts) {
31872
32533
  seedCountRaw,
31873
32534
  seedCount,
31874
32535
  warning
31875
- }).where(eq31(discoverySessions.id, opts.sessionId)).run();
32536
+ }).where(eq33(discoverySessions.id, opts.sessionId)).run();
31876
32537
  const probeRows = [];
31877
32538
  const buckets = { cited: 0, aspirational: 0, "wasted-surface": 0 };
31878
32539
  for (const query of probedCanonicals) {
@@ -31885,7 +32546,7 @@ async function executeDiscovery(opts) {
31885
32546
  probeRows.push({ citedDomains: probe.citedDomains, bucket });
31886
32547
  buckets[bucket]++;
31887
32548
  opts.db.insert(discoveryProbes).values({
31888
- id: crypto26.randomUUID(),
32549
+ id: crypto27.randomUUID(),
31889
32550
  sessionId: opts.sessionId,
31890
32551
  projectId: opts.project.id,
31891
32552
  query,
@@ -31912,7 +32573,7 @@ async function executeDiscovery(opts) {
31912
32573
  wastedCount: buckets["wasted-surface"],
31913
32574
  competitorMap,
31914
32575
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
31915
- }).where(eq31(discoverySessions.id, opts.sessionId)).run();
32576
+ }).where(eq33(discoverySessions.id, opts.sessionId)).run();
31916
32577
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
31917
32578
  return {
31918
32579
  buckets,
@@ -31929,7 +32590,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
31929
32590
  const domain = normalizeDomain(entry.domain);
31930
32591
  if (!domain) continue;
31931
32592
  db.insert(domainClassifications).values({
31932
- id: crypto26.randomUUID(),
32593
+ id: crypto27.randomUUID(),
31933
32594
  projectId,
31934
32595
  domain,
31935
32596
  competitorType: entry.competitorType,
@@ -31952,7 +32613,7 @@ function markSessionFailed(db, sessionId, error) {
31952
32613
  status: DiscoverySessionStatuses.failed,
31953
32614
  error,
31954
32615
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
31955
- }).where(eq31(discoverySessions.id, sessionId)).run();
32616
+ }).where(eq33(discoverySessions.id, sessionId)).run();
31956
32617
  }
31957
32618
  function dedupeStrings(input) {
31958
32619
  const seen = /* @__PURE__ */ new Set();
@@ -31969,8 +32630,8 @@ function dedupeStrings(input) {
31969
32630
  }
31970
32631
 
31971
32632
  // ../api-routes/src/technical-aeo.ts
31972
- import crypto27 from "crypto";
31973
- 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";
31974
32635
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
31975
32636
  function emptyScore(projectName) {
31976
32637
  return {
@@ -32002,10 +32663,10 @@ function parsePositiveInt(value, fallback, max) {
32002
32663
  async function technicalAeoRoutes(app, opts) {
32003
32664
  app.get("/projects/:name/technical-aeo", async (request) => {
32004
32665
  const project = resolveProject(app.db, request.params.name);
32005
- const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq32(siteAuditSnapshots.runId, runs.id)).where(and23(
32006
- eq32(siteAuditSnapshots.projectId, project.id),
32007
- eq32(runs.kind, RunKinds["site-audit"]),
32008
- 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),
32009
32670
  notProbeRun()
32010
32671
  )).orderBy(desc16(siteAuditSnapshots.createdAt)).limit(2).all();
32011
32672
  const latest = rows[0];
@@ -32037,24 +32698,24 @@ async function technicalAeoRoutes(app, opts) {
32037
32698
  });
32038
32699
  app.get("/projects/:name/technical-aeo/pages", async (request) => {
32039
32700
  const project = resolveProject(app.db, request.params.name);
32040
- const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq32(siteAuditSnapshots.runId, runs.id)).where(and23(
32041
- eq32(siteAuditSnapshots.projectId, project.id),
32042
- eq32(runs.kind, RunKinds["site-audit"]),
32043
- 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),
32044
32705
  notProbeRun()
32045
32706
  )).orderBy(desc16(siteAuditSnapshots.createdAt)).limit(1).get();
32046
32707
  if (!latest) {
32047
32708
  return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
32048
32709
  }
32049
32710
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
32050
- const conds = [eq32(siteAuditPages.runId, latest.runId)];
32051
- if (statusFilter) conds.push(eq32(siteAuditPages.status, statusFilter));
32052
- 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);
32053
32714
  const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
32054
32715
  const total = totalRow?.value ?? 0;
32055
32716
  const limit = parsePositiveInt(request.query.limit, 100, 500);
32056
32717
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
32057
- 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);
32058
32719
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
32059
32720
  const pages = rows.map((row) => ({
32060
32721
  url: row.url,
@@ -32073,10 +32734,10 @@ async function technicalAeoRoutes(app, opts) {
32073
32734
  auditedAt: siteAuditSnapshots.auditedAt,
32074
32735
  aggregateScore: siteAuditSnapshots.aggregateScore,
32075
32736
  pagesAudited: siteAuditSnapshots.pagesAudited
32076
- }).from(siteAuditSnapshots).innerJoin(runs, eq32(siteAuditSnapshots.runId, runs.id)).where(and23(
32077
- eq32(siteAuditSnapshots.projectId, project.id),
32078
- eq32(runs.kind, RunKinds["site-audit"]),
32079
- 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),
32080
32741
  notProbeRun()
32081
32742
  )).orderBy(desc16(siteAuditSnapshots.createdAt)).limit(limit).all();
32082
32743
  return { project: project.name, points: rows.reverse() };
@@ -32087,16 +32748,16 @@ async function technicalAeoRoutes(app, opts) {
32087
32748
  if (!parsed.success) {
32088
32749
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
32089
32750
  }
32090
- const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and23(
32091
- eq32(runs.projectId, project.id),
32092
- eq32(runs.kind, RunKinds["site-audit"]),
32093
- 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])
32094
32755
  )).get();
32095
32756
  if (existing) {
32096
32757
  return { runId: existing.id, status: existing.status };
32097
32758
  }
32098
32759
  const now = (/* @__PURE__ */ new Date()).toISOString();
32099
- const runId = crypto27.randomUUID();
32760
+ const runId = crypto28.randomUUID();
32100
32761
  app.db.insert(runs).values({
32101
32762
  id: runId,
32102
32763
  projectId: project.id,
@@ -32223,6 +32884,11 @@ async function apiRoutes(app, opts) {
32223
32884
  getTelemetryStatus: opts.getTelemetryStatus,
32224
32885
  setTelemetryEnabled: opts.setTelemetryEnabled
32225
32886
  });
32887
+ await api.register(adsRoutes, {
32888
+ adsCredentialStore: opts.adsCredentialStore,
32889
+ verifyAdsAccount: opts.verifyAdsAccount,
32890
+ onAdsSyncRequested: opts.onAdsSyncRequested
32891
+ });
32226
32892
  await api.register(bingRoutes, {
32227
32893
  bingConnectionStore: opts.bingConnectionStore,
32228
32894
  onInspectSitemapRequested: opts.onBingInspectSitemapRequested
@@ -32286,6 +32952,7 @@ async function apiRoutes(app, opts) {
32286
32952
  bingConnectionStore: opts.bingConnectionStore,
32287
32953
  wordpressConnectionStore: opts.wordpressConnectionStore,
32288
32954
  ga4CredentialStore: opts.ga4CredentialStore,
32955
+ adsCredentialStore: opts.adsCredentialStore,
32289
32956
  getGoogleAuthConfig: opts.getGoogleAuthConfig,
32290
32957
  getPlacesConfig: opts.getPlacesConfig,
32291
32958
  publicUrl: opts.publicUrl,
@@ -32436,7 +33103,7 @@ function buildTrafficSourceValidators(opts) {
32436
33103
  }
32437
33104
 
32438
33105
  // src/intelligence-service.ts
32439
- import crypto28 from "crypto";
33106
+ import crypto29 from "crypto";
32440
33107
 
32441
33108
  // src/logger.ts
32442
33109
  var IS_TTY = process.stdout.isTTY === true;
@@ -32668,9 +33335,9 @@ var IntelligenceService = class {
32668
33335
  */
32669
33336
  analyzeAndPersist(runId, projectId) {
32670
33337
  const recentRuns = this.db.select().from(runs).where(
32671
- and24(
32672
- eq33(runs.projectId, projectId),
32673
- 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")),
32674
33341
  // Defensive: RunCoordinator already skips probes before this is
32675
33342
  // called, but if a future call site invokes analyzeAndPersist
32676
33343
  // directly for a probe, probes still must not pollute the
@@ -32752,7 +33419,7 @@ var IntelligenceService = class {
32752
33419
  * Returns the persisted insights so the coordinator can count critical/high.
32753
33420
  */
32754
33421
  analyzeAndPersistGbp(runId, projectId) {
32755
- 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();
32756
33423
  if (!runRow) {
32757
33424
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
32758
33425
  this.persistGbpInsights(runId, projectId, [], []);
@@ -32760,11 +33427,11 @@ var IntelligenceService = class {
32760
33427
  }
32761
33428
  const windowStart = runRow.startedAt ?? runRow.createdAt;
32762
33429
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
32763
- const selected = this.db.select().from(gbpLocations).where(and24(
32764
- eq33(gbpLocations.projectId, projectId),
32765
- eq33(gbpLocations.selected, true),
32766
- gte6(gbpLocations.syncedAt, windowStart),
32767
- 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)
32768
33435
  )).all();
32769
33436
  if (selected.length === 0) {
32770
33437
  log.info("gbp-intelligence.skip", { runId, reason: "no locations synced during run" });
@@ -32797,10 +33464,10 @@ var IntelligenceService = class {
32797
33464
  }
32798
33465
  /** Build the per-location signal bundle the GBP analyzer consumes. */
32799
33466
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
32800
- 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();
32801
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and24(eq33(gbpPlaceActions.projectId, projectId), eq33(gbpPlaceActions.locationName, locationName))).all();
32802
- 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();
32803
- 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();
32804
33471
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
32805
33472
  const summary = buildGbpSummary({
32806
33473
  locationName,
@@ -32832,7 +33499,7 @@ var IntelligenceService = class {
32832
33499
  /** Build the month-over-month keyword series for a location from the
32833
33500
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
32834
33501
  buildGbpKeywordTrend(projectId, locationName) {
32835
- 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();
32836
33503
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
32837
33504
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
32838
33505
  const recentMonth = months[0] ?? null;
@@ -32863,7 +33530,7 @@ var IntelligenceService = class {
32863
33530
  */
32864
33531
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
32865
33532
  const covered = new Set(coveredLocationNames);
32866
- 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();
32867
33534
  const staleIds = [];
32868
33535
  const dismissedSlots = /* @__PURE__ */ new Set();
32869
33536
  for (const row of existing) {
@@ -32874,7 +33541,7 @@ var IntelligenceService = class {
32874
33541
  }
32875
33542
  this.db.transaction((tx) => {
32876
33543
  for (const id of staleIds) {
32877
- tx.delete(insights).where(eq33(insights.id, id)).run();
33544
+ tx.delete(insights).where(eq35(insights.id, id)).run();
32878
33545
  }
32879
33546
  for (const insight of gbpInsights) {
32880
33547
  const parsed = parseGbpInsightId(insight.id);
@@ -32952,7 +33619,7 @@ var IntelligenceService = class {
32952
33619
  * create per run + aggregate). DB is left untouched.
32953
33620
  */
32954
33621
  backfill(projectName, opts, onProgress) {
32955
- 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();
32956
33623
  if (!project) {
32957
33624
  throw new Error(`Project "${projectName}" not found`);
32958
33625
  }
@@ -32965,13 +33632,13 @@ var IntelligenceService = class {
32965
33632
  sinceTimestamp = parsed;
32966
33633
  }
32967
33634
  const allRuns = this.db.select().from(runs).where(
32968
- and24(
32969
- eq33(runs.projectId, project.id),
32970
- 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")),
32971
33638
  // Backfill must not replay probe runs as if they were real sweeps.
32972
33639
  ne5(runs.trigger, RunTriggers.probe)
32973
33640
  )
32974
- ).orderBy(asc4(runs.finishedAt)).all();
33641
+ ).orderBy(asc5(runs.finishedAt)).all();
32975
33642
  let startIdx = 0;
32976
33643
  let endIdx = allRuns.length;
32977
33644
  if (opts?.fromRunId) {
@@ -33000,7 +33667,7 @@ var IntelligenceService = class {
33000
33667
  let wouldDeleteTotal = 0;
33001
33668
  const existingByRunId = /* @__PURE__ */ new Map();
33002
33669
  if (isDryRun && targetRuns.length > 0) {
33003
- 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();
33004
33671
  for (const r of rows) {
33005
33672
  if (r.runId == null) continue;
33006
33673
  existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
@@ -33046,7 +33713,7 @@ var IntelligenceService = class {
33046
33713
  return { processed, skipped, totalInsights };
33047
33714
  }
33048
33715
  loadTrackedCompetitors(projectId) {
33049
- 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);
33050
33717
  }
33051
33718
  /**
33052
33719
  * Wipe transition signals from an analysis result while keeping health.
@@ -33067,15 +33734,15 @@ var IntelligenceService = class {
33067
33734
  }
33068
33735
  persistResult(result, runId, projectId) {
33069
33736
  const previouslyDismissed = /* @__PURE__ */ new Set();
33070
- 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();
33071
33738
  for (const row of existingInsights) {
33072
33739
  if (row.dismissed) {
33073
33740
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
33074
33741
  }
33075
33742
  }
33076
33743
  this.db.transaction((tx) => {
33077
- tx.delete(insights).where(eq33(insights.runId, runId)).run();
33078
- 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();
33079
33746
  const now = (/* @__PURE__ */ new Date()).toISOString();
33080
33747
  for (const insight of result.insights) {
33081
33748
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -33095,7 +33762,7 @@ var IntelligenceService = class {
33095
33762
  }).run();
33096
33763
  }
33097
33764
  tx.insert(healthSnapshots).values({
33098
- id: crypto28.randomUUID(),
33765
+ id: crypto29.randomUUID(),
33099
33766
  projectId,
33100
33767
  runId,
33101
33768
  overallCitedRate: String(result.health.overallCitedRate),
@@ -33126,24 +33793,24 @@ var IntelligenceService = class {
33126
33793
  applySeverityTiering(rawInsights, excludeRunId, projectId) {
33127
33794
  const regressions = rawInsights.filter((i) => i.type === "regression");
33128
33795
  if (regressions.length === 0) return rawInsights;
33129
- 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();
33130
33797
  const gscConnected = gscRows.length > 0;
33131
33798
  const gscImpressionsByQuery = /* @__PURE__ */ new Map();
33132
33799
  for (const row of gscRows) {
33133
33800
  const key = row.query.toLowerCase();
33134
33801
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
33135
33802
  }
33136
- 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();
33137
33804
  const locationCount = Math.max(
33138
33805
  1,
33139
33806
  (projectRow?.locations ?? []).length
33140
33807
  );
33141
33808
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
33142
33809
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
33143
- and24(
33144
- eq33(runs.projectId, projectId),
33145
- eq33(runs.kind, RunKinds["answer-visibility"]),
33146
- 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")),
33147
33814
  // Defensive — see top of file.
33148
33815
  ne5(runs.trigger, RunTriggers.probe)
33149
33816
  )
@@ -33163,7 +33830,7 @@ var IntelligenceService = class {
33163
33830
  const haveHistory = recentRunIds.length > 0;
33164
33831
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
33165
33832
  if (haveHistory) {
33166
- 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();
33167
33834
  const regressionGroups = /* @__PURE__ */ new Map();
33168
33835
  for (const row of priorRows) {
33169
33836
  if (!row.runId) continue;
@@ -33192,7 +33859,7 @@ var IntelligenceService = class {
33192
33859
  });
33193
33860
  }
33194
33861
  buildRunData(runId, projectId, completedAt, location = null) {
33195
- 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();
33196
33863
  const projectDomains = projectDomainRow ? effectiveDomains({
33197
33864
  canonicalDomain: projectDomainRow.canonicalDomain,
33198
33865
  ownedDomains: projectDomainRow.ownedDomains
@@ -33208,7 +33875,7 @@ var IntelligenceService = class {
33208
33875
  citedDomains: querySnapshots.citedDomains,
33209
33876
  competitorOverlap: querySnapshots.competitorOverlap,
33210
33877
  snapshotLocation: querySnapshots.location
33211
- }).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();
33212
33879
  const snapshots = [];
33213
33880
  let orphanCount = 0;
33214
33881
  for (const r of rows) {
@@ -33283,6 +33950,11 @@ export {
33283
33950
  gbpPlaceActions,
33284
33951
  gbpLodgingSnapshots,
33285
33952
  gbpPlaceDetails,
33953
+ adsConnections,
33954
+ adsCampaigns,
33955
+ adsAdGroups,
33956
+ adsAds,
33957
+ adsInsightsDaily,
33286
33958
  createClient,
33287
33959
  parseJsonColumn,
33288
33960
  extractLegacyCredentials,