@canonry/canonry 7.94.0 → 7.96.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/aero/references/ai-traffic-tracking.md +4 -1
- package/assets/agent-workspace/skills/canonry/references/ai-traffic-tracking.md +4 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +12 -4
- package/assets/assets/{AgentSetupPage-BlPXN_oH.js → AgentSetupPage-DBiPrrdJ.js} +4 -4
- package/assets/assets/{AuditHistoryPanel-N8Z4gng7.js → AuditHistoryPanel-DSnIs064.js} +1 -1
- package/assets/assets/{BacklinksPage-DSrgqlkj.js → BacklinksPage-tPiG0nEP.js} +1 -1
- package/assets/assets/{HistoryPage-v2jbUkle.js → HistoryPage-sxYHrfNk.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-B8EbifR2.js +4 -0
- package/assets/assets/ProjectPage-C1_A7iGy.js +11 -0
- package/assets/assets/{RunsPage-CXZJDdnr.js → RunsPage-RE6nLBTv.js} +1 -1
- package/assets/assets/{SettingsPage-CeLvWs_T.js → SettingsPage-Dn93SUD1.js} +1 -1
- package/assets/assets/SiteHealthSection-BEBYoCNu.js +2 -0
- package/assets/assets/ToneBadge-DFlmWh2A.js +60 -0
- package/assets/assets/{TrafficPage-CEsmS5N1.js → TrafficPage-cNokjIJa.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-D31g1FXV.js → TrafficSourceDetailPage-DFGbx0zs.js} +1 -1
- package/assets/assets/{account-context-CGIyok0O.js → account-context-RM43ek4w.js} +1 -1
- package/assets/assets/{agent-trust-copy-B-3akxrd.js → agent-trust-copy-CRZ_oe24.js} +1 -1
- package/assets/assets/{card-BMjN_ofC.js → card-GFeSoV1m.js} +1 -1
- package/assets/assets/{format-helpers-Blu0QNcd.js → format-helpers-4dA9HFgE.js} +1 -1
- package/assets/assets/index-CohgAQ9L.js +60 -0
- package/assets/assets/{query-client-DdwTc28X.js → query-client-B3Z7Vqgv.js} +1 -1
- package/assets/assets/react-query-HC0PcMhZ.js +1 -0
- package/assets/assets/{react-sigma_core.esm.min-5sP0Pb_F.js → react-sigma_core.esm.min-IsYtRW95.js} +1 -1
- package/assets/assets/{server-traffic-403Q1CKm.js → server-traffic-BtWOYZXf.js} +1 -1
- package/assets/assets/{service-ChEbo8oM.js → service-Bu4M0T0x.js} +1 -1
- package/assets/index.html +7 -7
- package/dist/{ads-6RVS23I5.js → ads-NS4KNR2U.js} +3 -3
- package/dist/{agent-N5LH7QNA.js → agent-773TJYGJ.js} +7 -7
- package/dist/{agent-ask-IJV2PWPN.js → agent-ask-MQXNRMAN.js} +3 -3
- package/dist/{agent-conversations-STISIDWB.js → agent-conversations-5EEEEWEQ.js} +3 -3
- package/dist/{agent-journeys-VCP5MD2W.js → agent-journeys-U5ITMUIM.js} +3 -3
- package/dist/{agent-memory-XQQNXRY6.js → agent-memory-6PEE54ST.js} +3 -3
- package/dist/{agent-providers-IW2Q6WPP.js → agent-providers-6D76GSYA.js} +3 -3
- package/dist/{agent-transcript-5XUCHOOT.js → agent-transcript-6M5RYZIR.js} +3 -3
- package/dist/{analytics-RZZZBC4H.js → analytics-WBFQJTHV.js} +3 -3
- package/dist/{apply-X5ZTHGSJ.js → apply-AGNDBHNV.js} +3 -3
- package/dist/{backfill-ZF6RX27O.js → backfill-ZDPS7VON.js} +10 -10
- package/dist/{backlinks-WZ7YUKHH.js → backlinks-TNPNJXPS.js} +3 -3
- package/dist/{bing-Z4A5NHOC.js → bing-5WB7JCBP.js} +3 -3
- package/dist/{bootstrap-OCJ7EIXK.js → bootstrap-XF77XL7U.js} +10 -10
- package/dist/{cdp-YGZFLTFT.js → cdp-NBTJLUPM.js} +7 -7
- package/dist/{chunk-RSBIHKHH.js → chunk-24CUAPPY.js} +4 -4
- package/dist/{chunk-M6DYUCNB.js → chunk-35AEUG77.js} +1 -1
- package/dist/{chunk-S6KJYLOV.js → chunk-377PJ4AT.js} +3 -3
- package/dist/{chunk-R7BX2ZSQ.js → chunk-3FHPUOMW.js} +1 -1
- package/dist/{chunk-K63XX7VY.js → chunk-4GECW7XZ.js} +3 -3
- package/dist/{chunk-3D4VEWDJ.js → chunk-5VLCPNTR.js} +6 -6
- package/dist/{chunk-SFNSSHIN.js → chunk-6X24IDPZ.js} +1 -1
- package/dist/{chunk-XPBEDIXW.js → chunk-CPJX2RWA.js} +2 -2
- package/dist/{chunk-PR5FCYNN.js → chunk-F2O42DSO.js} +21 -21
- package/dist/{chunk-D6KTO5TO.js → chunk-FUMYCZ62.js} +1 -1
- package/dist/{chunk-ECUJEKIO.js → chunk-G6JSK2RY.js} +1 -1
- package/dist/{chunk-YRCKRY3Q.js → chunk-HKUH75YH.js} +2 -2
- package/dist/{chunk-XIHIB7VP.js → chunk-HN2YNYPO.js} +7 -7
- package/dist/{chunk-IUDXV6PH.js → chunk-I663X7WB.js} +2 -2
- package/dist/{chunk-RJLSGCOI.js → chunk-IO4BZLM7.js} +2 -2
- package/dist/{chunk-BL4CZJYJ.js → chunk-JGLX5WU6.js} +115 -3
- package/dist/{chunk-NUBADAA4.js → chunk-JLC3CCFM.js} +4 -4
- package/dist/{chunk-VGAQH7KI.js → chunk-KCKUCJOG.js} +1 -1
- package/dist/{chunk-55EFIQP7.js → chunk-LXKWFM74.js} +2 -2
- package/dist/{chunk-N6MXZ5SC.js → chunk-MAOKDX3W.js} +5 -5
- package/dist/{chunk-UPBQHUE6.js → chunk-MJIL4BWR.js} +3 -3
- package/dist/{chunk-BJAOD3NW.js → chunk-N4SEITGT.js} +5 -5
- package/dist/{chunk-QDGW75MP.js → chunk-OCXDY2DG.js} +1 -1
- package/dist/{chunk-B5JKQXRN.js → chunk-PC7ZUW2H.js} +2 -2
- package/dist/{chunk-N7PGV6QQ.js → chunk-PSCXQGJE.js} +1027 -816
- package/dist/{chunk-LXJAXAJD.js → chunk-QBIIQF3J.js} +1 -1
- package/dist/{chunk-32N7BGTD.js → chunk-S3HNS2YJ.js} +3 -3
- package/dist/{chunk-CBNDEKI6.js → chunk-SDCH5EBA.js} +1 -1
- package/dist/{chunk-K2JCPF2W.js → chunk-SPWGDODX.js} +368 -368
- package/dist/{chunk-LDY66O7A.js → chunk-SXHD2NFT.js} +4 -4
- package/dist/{chunk-7JQM46YO.js → chunk-UPALIEP2.js} +2 -2
- package/dist/{chunk-NSUB2S4V.js → chunk-W4ZRFNOX.js} +57 -26
- package/dist/{chunk-JDOAULVX.js → chunk-XOCSSHYD.js} +1 -1
- package/dist/{chunk-FM74QGW5.js → chunk-YL77WFHQ.js} +7 -7
- package/dist/{chunk-YGHI3W4D.js → chunk-ZZVRDHSK.js} +1 -1
- package/dist/{citations-IUITVPHO.js → citations-MZ6XQDI3.js} +3 -3
- package/dist/{cli-catalog-IAPKU2RG.js → cli-catalog-QPB2ADGD.js} +3 -3
- package/dist/{cli-commands-FICINBUR.js → cli-commands-RRHOMF6X.js} +7 -7
- package/dist/cli.js +13 -13
- package/dist/{competitor-U5DMMCIA.js → competitor-VF77UMUR.js} +3 -3
- package/dist/{content-ZQIZUNF5.js → content-BINAMQFF.js} +3 -3
- package/dist/{dashboard-KPGX4EQT.js → dashboard-OE4BGMKP.js} +6 -6
- package/dist/{demo-NEWVCEXD.js → demo-3HVJUP3G.js} +1 -1
- package/dist/{demo-server-5GJEGG5K.js → demo-server-BEFPBA7P.js} +14 -14
- package/dist/{discover-HSPVFHKK.js → discover-3XUPBCXL.js} +3 -3
- package/dist/{discover-eval-BYKPJ3DL.js → discover-eval-3DWJPIU2.js} +3 -3
- package/dist/{doctor-CK4BTTWH.js → doctor-KYWVYS74.js} +3 -3
- package/dist/{evidence-BX3CWPCT.js → evidence-DCTXENHR.js} +3 -3
- package/dist/{export-cmd-QMCRDMPZ.js → export-cmd-E5OUTSMR.js} +3 -3
- package/dist/{ga-RMTUMSEJ.js → ga-KY7MHW2G.js} +4 -4
- package/dist/{gbp-JK67SYLW.js → gbp-2TJ6JACW.js} +3 -3
- package/dist/{google-PJSK6IJV.js → google-BZKYIIA6.js} +4 -4
- package/dist/{google-marketing-UQ2FONXU.js → google-marketing-6ACZP4JB.js} +1 -1
- package/dist/{health-cmd-ZDXFD53P.js → health-cmd-LCVJY5MG.js} +3 -3
- package/dist/{history-OYL7SPEY.js → history-MAZOPZZ5.js} +3 -3
- package/dist/index.d.ts +213 -35
- package/dist/index.js +25 -25
- package/dist/{init-DZUNAQBN.js → init-Z35R7A6X.js} +12 -12
- package/dist/{insights-AIBLDPX6.js → insights-PAZHZP74.js} +3 -3
- package/dist/intelligence-service-YA7PBFQV.js +16 -0
- package/dist/{journeys-EDEET2EC.js → journeys-H6OPK5XY.js} +3 -3
- package/dist/{keys-SEZQTYGJ.js → keys-DF6LV4T5.js} +3 -3
- package/dist/{keyword-6FJIWTHW.js → keyword-ILICCRQK.js} +3 -3
- package/dist/{logs-EML2KKOP.js → logs-PKKCPDIN.js} +3 -3
- package/dist/{mcp-AA5QUNBZ.js → mcp-7GVOYYX4.js} +4 -4
- package/dist/mcp.js +9 -9
- package/dist/{measurement-plan-ALHRGRPH.js → measurement-plan-V7E7UHE2.js} +5 -5
- package/dist/{notify-EVTI3SWQ.js → notify-ISU7XBAW.js} +3 -3
- package/dist/{organic-evidence-EXQQ6R7H.js → organic-evidence-FIL7YJ4Z.js} +3 -3
- package/dist/{overview-5CIFLSDG.js → overview-NVBLGY53.js} +4 -4
- package/dist/{project-DNDENHRU.js → project-NSNMLL3Z.js} +3 -3
- package/dist/{query-ETMHFVKZ.js → query-TPZUUJ4X.js} +3 -3
- package/dist/{research-Q5X46CR5.js → research-WILAIDCK.js} +3 -3
- package/dist/{results-export-DZZANVYY.js → results-export-XP7XUKI2.js} +3 -3
- package/dist/{run-OQJXRJ42.js → run-L646IKUB.js} +4 -4
- package/dist/{schedule-6AI3J2AB.js → schedule-I2LW344O.js} +3 -3
- package/dist/{search-7TAJBSHE.js → search-7EBWXQ6X.js} +3 -3
- package/dist/{serve-WD44V3TK.js → serve-LNBKTV5N.js} +26 -26
- package/dist/{session-LTKCS763.js → session-U2OPQSJE.js} +13 -13
- package/dist/{settings-EUMYGNIV.js → settings-IGJ6ZA7C.js} +3 -3
- package/dist/{sitemaps-I4QPLAZW.js → sitemaps-B4G4PXIE.js} +3 -3
- package/dist/{skills-PTYYU3FR.js → skills-WQXXY2HN.js} +2 -2
- package/dist/{snapshot-62FF4GEA.js → snapshot-2TD4WIEO.js} +3 -3
- package/dist/{src-LRYXRHLW.js → src-DI4RNYAG.js} +4 -2
- package/dist/{status-VRWPYTY3.js → status-SKGGPZFE.js} +4 -4
- package/dist/{technical-aeo-OKZW2GAX.js → technical-aeo-AFAXF5M5.js} +3 -3
- package/dist/{telemetry-RRTHKD6W.js → telemetry-ZRRX7I75.js} +4 -4
- package/dist/{tools-F7WCCKZW.js → tools-RY7ZXQJ5.js} +11 -11
- package/dist/{traffic-H6RDZWGG.js → traffic-LA3FZKID.js} +9 -9
- package/dist/{trends-RZOQW2LE.js → trends-GUUBTBCO.js} +3 -3
- package/dist/{users-KFBCBA3V.js → users-W44TRUSL.js} +3 -3
- package/dist/{visibility-stats-53OZR3UN.js → visibility-stats-KMKV4TF5.js} +3 -3
- package/dist/{wordpress-2BXU6FDI.js → wordpress-FNLXVJDO.js} +3 -3
- package/package.json +1 -1
- package/assets/assets/MeasurementPropertyPage-C1eIWJPD.js +0 -4
- package/assets/assets/ProjectPage-Ho61hE8f.js +0 -11
- package/assets/assets/SiteHealthSection-C2Ys7rzL.js +0 -2
- package/assets/assets/ToneBadge-C49669Ol.js +0 -60
- package/assets/assets/index-B-t8djQ-.js +0 -60
- package/assets/assets/react-query-dnbkXy7A.js +0 -1
- package/dist/intelligence-service-YLNZTJQA.js +0 -16
|
@@ -17,14 +17,14 @@ import {
|
|
|
17
17
|
normalizeTrafficPathPattern,
|
|
18
18
|
sessionizeJourneyRequests,
|
|
19
19
|
verifyRequestSignature
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-OCXDY2DG.js";
|
|
21
21
|
import {
|
|
22
22
|
currentOutcomeAttribution,
|
|
23
23
|
getRequestContext,
|
|
24
24
|
registerRequestContext,
|
|
25
25
|
requestAuditIdentity,
|
|
26
26
|
sanitizeRequestContext
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-SDCH5EBA.js";
|
|
28
28
|
import {
|
|
29
29
|
RawEventSampleEventTypes,
|
|
30
30
|
SentimentIdempotencyConflict,
|
|
@@ -87,6 +87,7 @@ import {
|
|
|
87
87
|
gaSearchLandingPages,
|
|
88
88
|
gaSearchLandingWindows,
|
|
89
89
|
gaSocialReferrals,
|
|
90
|
+
gaSyncCoverage,
|
|
90
91
|
gaTrafficSnapshots,
|
|
91
92
|
gaTrafficSummaries,
|
|
92
93
|
gaTrafficWindowSummaries,
|
|
@@ -183,7 +184,7 @@ import {
|
|
|
183
184
|
userSessions,
|
|
184
185
|
users,
|
|
185
186
|
writeNegativeReviewMaxStars
|
|
186
|
-
} from "./chunk-
|
|
187
|
+
} from "./chunk-JGLX5WU6.js";
|
|
187
188
|
import {
|
|
188
189
|
ADS_ACTIVATE_SCOPE,
|
|
189
190
|
ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
|
|
@@ -274,6 +275,8 @@ import {
|
|
|
274
275
|
GA4_AI_ASSISTANT_CHANNEL_GROUP,
|
|
275
276
|
GA_OVERVIEW_HISTORY_DAYS,
|
|
276
277
|
GA_OVERVIEW_METRIC_ORDER,
|
|
278
|
+
GA_OVERVIEW_WINDOWS,
|
|
279
|
+
GA_OVERVIEW_WINDOW_DAYS,
|
|
277
280
|
GA_SEARCH_LANDING_DEFAULT_LIMIT,
|
|
278
281
|
GA_SEARCH_LANDING_DEFAULT_WINDOW,
|
|
279
282
|
GA_SEARCH_LANDING_MAX_LIMIT,
|
|
@@ -1237,6 +1240,7 @@ import {
|
|
|
1237
1240
|
trafficSampleClassifierSchema,
|
|
1238
1241
|
trafficSamplesQuerySchema,
|
|
1239
1242
|
trafficSamplesResponseSchema,
|
|
1243
|
+
trafficSeriesGranularitySchema,
|
|
1240
1244
|
trafficSourceDetailDtoSchema,
|
|
1241
1245
|
trafficSourceDtoSchema,
|
|
1242
1246
|
trafficSourceListResponseSchema,
|
|
@@ -1280,7 +1284,7 @@ import {
|
|
|
1280
1284
|
wordpressSchemaDeployResultDtoSchema,
|
|
1281
1285
|
wordpressSchemaStatusResultDtoSchema,
|
|
1282
1286
|
wordpressStatusDtoSchema
|
|
1283
|
-
} from "./chunk-
|
|
1287
|
+
} from "./chunk-W4ZRFNOX.js";
|
|
1284
1288
|
|
|
1285
1289
|
// ../api-routes/src/user-session.ts
|
|
1286
1290
|
import crypto2 from "crypto";
|
|
@@ -31582,17 +31586,7 @@ function emptyAiCounts() {
|
|
|
31582
31586
|
}
|
|
31583
31587
|
var AI_ATTRIBUTION_PREFERRED_DIMENSION = "session";
|
|
31584
31588
|
function pickWinningAttributionDimension(sessionsByDimension) {
|
|
31585
|
-
|
|
31586
|
-
let winnerSessions = -1;
|
|
31587
|
-
for (const [dimension, sessions] of sessionsByDimension) {
|
|
31588
|
-
const beatsOnCount = sessions > winnerSessions;
|
|
31589
|
-
const winsTie = sessions === winnerSessions && dimension === AI_ATTRIBUTION_PREFERRED_DIMENSION;
|
|
31590
|
-
if (beatsOnCount || winsTie) {
|
|
31591
|
-
winnerSessions = sessions;
|
|
31592
|
-
winner = dimension;
|
|
31593
|
-
}
|
|
31594
|
-
}
|
|
31595
|
-
return winner;
|
|
31589
|
+
return sessionsByDimension.has(AI_ATTRIBUTION_PREFERRED_DIMENSION) ? AI_ATTRIBUTION_PREFERRED_DIMENSION : void 0;
|
|
31596
31590
|
}
|
|
31597
31591
|
function resolveWinningDimensions(rows) {
|
|
31598
31592
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -36493,11 +36487,15 @@ var analyticsWindowParameter = {
|
|
|
36493
36487
|
description: "Time window for analytics queries. An unrecognised value is rejected with 400; it is never widened to the full history.",
|
|
36494
36488
|
schema: { type: "string", enum: ["7d", "30d", "90d", "all"] }
|
|
36495
36489
|
};
|
|
36490
|
+
var gaTrafficWindowParameter = {
|
|
36491
|
+
...analyticsWindowParameter,
|
|
36492
|
+
schema: { type: "string", enum: [...WINDOW_SETS.gaTraffic] }
|
|
36493
|
+
};
|
|
36496
36494
|
var gaOverviewWindowParameter = {
|
|
36497
36495
|
name: "window",
|
|
36498
36496
|
in: "query",
|
|
36499
36497
|
description: 'Overview window: the complete days ending yesterday, as GA4 counts "Last 30 days". Defaults to 30d. Any other value is rejected with 400.',
|
|
36500
|
-
schema: { type: "string", enum: [
|
|
36498
|
+
schema: { type: "string", enum: [...WINDOW_SETS.gaOverview] }
|
|
36501
36499
|
};
|
|
36502
36500
|
var competitorLandscapeGroupKeyParameter = {
|
|
36503
36501
|
name: "groupKey",
|
|
@@ -42757,7 +42755,7 @@ var routeCatalog = [
|
|
|
42757
42755
|
{
|
|
42758
42756
|
name: "window",
|
|
42759
42757
|
in: "query",
|
|
42760
|
-
description: `Stored window, ending yesterday in the GA4 property time zone. 28d matches GA4's default "Last 28 days".`,
|
|
42758
|
+
description: `Stored 7, 28, 90 or 180-day window, ending yesterday in the GA4 property time zone. 28d matches GA4's default "Last 28 days".`,
|
|
42761
42759
|
schema: { type: "string", enum: [...gaSearchLandingWindowSchema.options], default: "28d" }
|
|
42762
42760
|
},
|
|
42763
42761
|
{
|
|
@@ -42812,7 +42810,7 @@ var routeCatalog = [
|
|
|
42812
42810
|
summary: "Get GA4 landing page traffic, channel breakdown, and AI referral landing pages",
|
|
42813
42811
|
description: "Every figure is measured over one window (windowStart to windowEnd). Each AI and social referral row carries share, its fraction of that table's sessions, so a table's shares add up to 1; each top page carries organicShare, its organic sessions over its own sessions.",
|
|
42814
42812
|
tags: ["ga4"],
|
|
42815
|
-
parameters: [nameParameter, limitQueryParameter,
|
|
42813
|
+
parameters: [nameParameter, limitQueryParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
|
|
42816
42814
|
responses: {
|
|
42817
42815
|
200: jsonResponse("GA4 traffic data returned.", "GaTrafficResponse"),
|
|
42818
42816
|
400: errorResponse("GA4 is not connected."),
|
|
@@ -42825,7 +42823,7 @@ var routeCatalog = [
|
|
|
42825
42823
|
summary: "Get raw AI referral detail rows per day, landing page, and attribution dimension",
|
|
42826
42824
|
description: "Detail rows, not totals. One row per landing page per attribution dimension, so a single day of one source is many rows and each is commonly worth one session. Use /ga/ai-referral-daily for per-date or per-source session counts.",
|
|
42827
42825
|
tags: ["ga4"],
|
|
42828
|
-
parameters: [nameParameter,
|
|
42826
|
+
parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
|
|
42829
42827
|
responses: {
|
|
42830
42828
|
200: jsonArrayResponse("AI referral history returned.", "GA4AiReferralHistoryEntry"),
|
|
42831
42829
|
400: errorResponse("GA4 is not connected."),
|
|
@@ -42838,7 +42836,7 @@ var routeCatalog = [
|
|
|
42838
42836
|
summary: "Get AI referral sessions per day and per source",
|
|
42839
42837
|
description: "Sums landing pages within one attribution dimension and never across dimensions, so totalSessions equals the aiSessionsDeduped reported by /ga/traffic for the same window. Sessions only: GA counts users as a distinct count at the grain requested, so an AI-referral user total cannot be summed from these rows and no un-dimensioned AI-referral fetch exists to supply one.",
|
|
42840
42838
|
tags: ["ga4"],
|
|
42841
|
-
parameters: [nameParameter,
|
|
42839
|
+
parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
|
|
42842
42840
|
responses: {
|
|
42843
42841
|
200: jsonResponse("AI referral daily series returned.", "GA4AiReferralDailyDto"),
|
|
42844
42842
|
400: errorResponse("GA4 is not connected."),
|
|
@@ -42850,7 +42848,7 @@ var routeCatalog = [
|
|
|
42850
42848
|
path: "/api/v1/projects/{name}/ga/social-referral-history",
|
|
42851
42849
|
summary: "Get social media referral sessions per day grouped by source",
|
|
42852
42850
|
tags: ["ga4"],
|
|
42853
|
-
parameters: [nameParameter,
|
|
42851
|
+
parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
|
|
42854
42852
|
responses: {
|
|
42855
42853
|
200: jsonArrayResponse("Social referral history returned.", "GA4SocialReferralHistoryEntry"),
|
|
42856
42854
|
400: errorResponse("GA4 is not connected."),
|
|
@@ -42891,7 +42889,7 @@ var routeCatalog = [
|
|
|
42891
42889
|
parameters: [nameParameter, gaOverviewWindowParameter],
|
|
42892
42890
|
responses: {
|
|
42893
42891
|
200: jsonResponse("GA4 overview returned.", "GaOverviewResponse"),
|
|
42894
|
-
400: errorResponse("GA4 is not connected, or the window is not 7d, 30d or
|
|
42892
|
+
400: errorResponse("GA4 is not connected, or the window is not 7d, 30d, 90d or 180d."),
|
|
42895
42893
|
404: errorResponse("Project not found.")
|
|
42896
42894
|
}
|
|
42897
42895
|
},
|
|
@@ -42900,7 +42898,7 @@ var routeCatalog = [
|
|
|
42900
42898
|
path: "/api/v1/projects/{name}/ga/session-history",
|
|
42901
42899
|
summary: "Get total sessions per day for the project",
|
|
42902
42900
|
tags: ["ga4"],
|
|
42903
|
-
parameters: [nameParameter,
|
|
42901
|
+
parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
|
|
42904
42902
|
responses: {
|
|
42905
42903
|
200: jsonArrayResponse("Session history returned.", "GA4SessionHistoryEntry"),
|
|
42906
42904
|
400: errorResponse("GA4 is not connected."),
|
|
@@ -44003,7 +44001,7 @@ var routeCatalog = [
|
|
|
44003
44001
|
{ name: "kind", in: "query", description: 'Filter to "crawler", "ai-user-fetch", "ai-referral", a comma-separated list of them (e.g. "ai-user-fetch,ai-referral"), or "all" (default). Rows, totals and series cover only the kinds asked for.', schema: stringSchema },
|
|
44004
44002
|
{ name: "limit", in: "query", description: "Max newest combined detail rows in the events array (default 500, max 5000).", schema: stringSchema },
|
|
44005
44003
|
{ name: "sourceId", in: "query", description: "Restrict to a single traffic source.", schema: stringSchema },
|
|
44006
|
-
{ name: "granularity", in: "query", description: 'Full-window series bucket size: "hour" (default) or "
|
|
44004
|
+
{ name: "granularity", in: "query", description: 'Full-window series bucket size: "hour" (default), "day" or "month" (UTC calendar months).', schema: { type: "string", enum: ["hour", "day", "month"] } }
|
|
44007
44005
|
],
|
|
44008
44006
|
responses: {
|
|
44009
44007
|
200: jsonResponse("Events returned with windowed totals.", "TrafficEventsResponse"),
|
|
@@ -47697,6 +47695,7 @@ var GA_PROPERTY_TABLES = [
|
|
|
47697
47695
|
gaTrafficWindowSummaries,
|
|
47698
47696
|
gaWindowMetrics,
|
|
47699
47697
|
gaDailyTotals,
|
|
47698
|
+
gaSyncCoverage,
|
|
47700
47699
|
gaAiReferrals,
|
|
47701
47700
|
gaSocialReferrals,
|
|
47702
47701
|
gaAcquisitionDaily,
|
|
@@ -47781,7 +47780,7 @@ var GA4_ADMIN_API_BASE = "https://analyticsadmin.googleapis.com/v1beta";
|
|
|
47781
47780
|
var GA4_SCOPE = "https://www.googleapis.com/auth/analytics.readonly";
|
|
47782
47781
|
var GOOGLE_TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
47783
47782
|
var GA4_DEFAULT_SYNC_DAYS = 30;
|
|
47784
|
-
var GA4_MAX_SYNC_DAYS =
|
|
47783
|
+
var GA4_MAX_SYNC_DAYS = 180;
|
|
47785
47784
|
function resolveGa4SyncDays(days) {
|
|
47786
47785
|
const requestedDays = days != null && Number.isFinite(days) ? Math.trunc(days) : GA4_DEFAULT_SYNC_DAYS;
|
|
47787
47786
|
const effectiveDays = Math.min(Math.max(1, requestedDays), GA4_MAX_SYNC_DAYS);
|
|
@@ -47859,6 +47858,7 @@ var GA4_METRICS = {
|
|
|
47859
47858
|
};
|
|
47860
47859
|
var GA4_SEARCH_LANDING_MAX_ROWS = 1e4;
|
|
47861
47860
|
var GA4_SEARCH_LANDING_PAGE_SIZE = 1e4;
|
|
47861
|
+
var GA4_SEARCH_LANDING_REPORT_DEFINITION = "search-landing-v1";
|
|
47862
47862
|
|
|
47863
47863
|
// ../integration-google-analytics/src/types.ts
|
|
47864
47864
|
var GA4ApiError = class extends Error {
|
|
@@ -48163,7 +48163,12 @@ function reportTimeZone(...responses) {
|
|
|
48163
48163
|
}
|
|
48164
48164
|
return null;
|
|
48165
48165
|
}
|
|
48166
|
-
function measurementDateRange(days) {
|
|
48166
|
+
function measurementDateRange(days, requestedRange, maxDays = GA4_MAX_SYNC_DAYS) {
|
|
48167
|
+
if (requestedRange) {
|
|
48168
|
+
const count10 = inclusiveDayCount(requestedRange.startDate, requestedRange.endDate);
|
|
48169
|
+
if (count10 === null || count10 > maxDays + 1) throw new GA4ApiError("Invalid GA4 sync date range", 400);
|
|
48170
|
+
return { syncDays: count10, dateRange: requestedRange };
|
|
48171
|
+
}
|
|
48167
48172
|
const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
|
|
48168
48173
|
const endDate = /* @__PURE__ */ new Date();
|
|
48169
48174
|
const startDate = /* @__PURE__ */ new Date();
|
|
@@ -48211,7 +48216,7 @@ async function fetchPagedReportWithTotals(accessToken, propertyId, baseRequest,
|
|
|
48211
48216
|
async function fetchAcquisitionByChannel(accessToken, propertyId, days, options) {
|
|
48212
48217
|
validateAccessToken2(accessToken);
|
|
48213
48218
|
validatePropertyId(propertyId);
|
|
48214
|
-
const { syncDays, dateRange } = measurementDateRange(days);
|
|
48219
|
+
const { syncDays, dateRange } = measurementDateRange(days, options?.dateRange);
|
|
48215
48220
|
const rows = await fetchPagedReport(accessToken, propertyId, {
|
|
48216
48221
|
dateRanges: [dateRange],
|
|
48217
48222
|
dimensions: [
|
|
@@ -48238,10 +48243,10 @@ async function fetchAcquisitionByChannel(accessToken, propertyId, days, options)
|
|
|
48238
48243
|
function isLandingPageDimensionIncompatibility(error) {
|
|
48239
48244
|
return error instanceof GA4ApiError && error.status === 400 && /landingPagePlusQueryString/i.test(error.message) && /incompatib/i.test(error.message);
|
|
48240
48245
|
}
|
|
48241
|
-
async function fetchLeadEvents(accessToken, propertyId, eventNames, days) {
|
|
48246
|
+
async function fetchLeadEvents(accessToken, propertyId, eventNames, days, dateRangeOverride) {
|
|
48242
48247
|
validateAccessToken2(accessToken);
|
|
48243
48248
|
validatePropertyId(propertyId);
|
|
48244
|
-
const { dateRange } = measurementDateRange(days);
|
|
48249
|
+
const { dateRange } = measurementDateRange(days, dateRangeOverride);
|
|
48245
48250
|
const filter = { orGroup: { expressions: eventNames.map((value) => ({ filter: {
|
|
48246
48251
|
fieldName: GA4_DIMENSIONS.eventName,
|
|
48247
48252
|
stringFilter: { matchType: "EXACT", value }
|
|
@@ -48327,13 +48332,10 @@ var AI_REFERRAL_PASSES = [
|
|
|
48327
48332
|
function isInvalidGa4Request(error) {
|
|
48328
48333
|
return error instanceof GA4ApiError && error.status === 400;
|
|
48329
48334
|
}
|
|
48330
|
-
async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
48335
|
+
async function fetchTrafficByLandingPage(accessToken, propertyId, days, dateRangeOverride) {
|
|
48331
48336
|
validateAccessToken2(accessToken);
|
|
48332
48337
|
validatePropertyId(propertyId);
|
|
48333
|
-
const {
|
|
48334
|
-
const endDate = /* @__PURE__ */ new Date();
|
|
48335
|
-
const startDate = /* @__PURE__ */ new Date();
|
|
48336
|
-
startDate.setDate(startDate.getDate() - syncDays);
|
|
48338
|
+
const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
|
|
48337
48339
|
ga4Log("info", "fetch-traffic.start", { propertyId, days: syncDays });
|
|
48338
48340
|
const PAGE_SIZE = 1e4;
|
|
48339
48341
|
const rows = [];
|
|
@@ -48342,7 +48344,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48342
48344
|
while (pageCount < GA4_MAX_PAGES) {
|
|
48343
48345
|
pageCount++;
|
|
48344
48346
|
const request = {
|
|
48345
|
-
dateRanges: [
|
|
48347
|
+
dateRanges: [dateRange],
|
|
48346
48348
|
dimensions: [
|
|
48347
48349
|
{ name: GA4_DIMENSIONS.date },
|
|
48348
48350
|
{ name: GA4_DIMENSIONS.landingPagePlusQueryString }
|
|
@@ -48367,9 +48369,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48367
48369
|
users: parseInt(row.metricValues[1].value, 10) || 0
|
|
48368
48370
|
}));
|
|
48369
48371
|
rows.push(...pageRows);
|
|
48370
|
-
const totalRows = response.rowCount
|
|
48372
|
+
const totalRows = response.rowCount;
|
|
48371
48373
|
offset += pageRows.length;
|
|
48372
|
-
if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
|
|
48374
|
+
if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
|
|
48375
|
+
if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
|
|
48373
48376
|
}
|
|
48374
48377
|
const organicMap = /* @__PURE__ */ new Map();
|
|
48375
48378
|
let organicOffset = 0;
|
|
@@ -48377,7 +48380,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48377
48380
|
while (organicPageCount < GA4_MAX_PAGES) {
|
|
48378
48381
|
organicPageCount++;
|
|
48379
48382
|
const organicRequest = {
|
|
48380
|
-
dateRanges: [
|
|
48383
|
+
dateRanges: [dateRange],
|
|
48381
48384
|
dimensions: [{ name: GA4_DIMENSIONS.date }, { name: GA4_DIMENSIONS.landingPagePlusQueryString }],
|
|
48382
48385
|
metrics: [{ name: GA4_METRICS.sessions }],
|
|
48383
48386
|
dimensionFilter: {
|
|
@@ -48395,9 +48398,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48395
48398
|
const key = `${row.dimensionValues[0].value}::${row.dimensionValues[1].value}`;
|
|
48396
48399
|
organicMap.set(key, parseInt(row.metricValues[0].value, 10) || 0);
|
|
48397
48400
|
}
|
|
48398
|
-
const total = organicResponse.rowCount
|
|
48401
|
+
const total = organicResponse.rowCount;
|
|
48399
48402
|
organicOffset += (organicResponse.rows ?? []).length;
|
|
48400
|
-
if ((organicResponse.rows ?? []).length < 1e4 || organicOffset >= total) break;
|
|
48403
|
+
if ((organicResponse.rows ?? []).length < 1e4 || total !== void 0 && organicOffset >= total) break;
|
|
48404
|
+
if (organicPageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 organic report truncated at the page limit", 502);
|
|
48401
48405
|
}
|
|
48402
48406
|
const directMap = /* @__PURE__ */ new Map();
|
|
48403
48407
|
let directOffset = 0;
|
|
@@ -48405,7 +48409,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48405
48409
|
while (directPageCount < GA4_MAX_PAGES) {
|
|
48406
48410
|
directPageCount++;
|
|
48407
48411
|
const directRequest = {
|
|
48408
|
-
dateRanges: [
|
|
48412
|
+
dateRanges: [dateRange],
|
|
48409
48413
|
dimensions: [{ name: GA4_DIMENSIONS.date }, { name: GA4_DIMENSIONS.landingPagePlusQueryString }],
|
|
48410
48414
|
metrics: [{ name: GA4_METRICS.sessions }],
|
|
48411
48415
|
dimensionFilter: {
|
|
@@ -48423,9 +48427,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48423
48427
|
const key = `${row.dimensionValues[0].value}::${row.dimensionValues[1].value}`;
|
|
48424
48428
|
directMap.set(key, parseInt(row.metricValues[0].value, 10) || 0);
|
|
48425
48429
|
}
|
|
48426
|
-
const total = directResponse.rowCount
|
|
48430
|
+
const total = directResponse.rowCount;
|
|
48427
48431
|
directOffset += (directResponse.rows ?? []).length;
|
|
48428
|
-
if ((directResponse.rows ?? []).length < 1e4 || directOffset >= total) break;
|
|
48432
|
+
if ((directResponse.rows ?? []).length < 1e4 || total !== void 0 && directOffset >= total) break;
|
|
48433
|
+
if (directPageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 direct report truncated at the page limit", 502);
|
|
48429
48434
|
}
|
|
48430
48435
|
for (const row of rows) {
|
|
48431
48436
|
const key = `${row.date}::${row.landingPage}`;
|
|
@@ -48592,7 +48597,8 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
|
|
|
48592
48597
|
validatePropertyId(propertyId);
|
|
48593
48598
|
const historyDays = Math.min(Math.max(1, Number.isFinite(days) ? Math.trunc(days) : GA_OVERVIEW_HISTORY_DAYS), GA_OVERVIEW_HISTORY_DAYS);
|
|
48594
48599
|
const requestedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
48595
|
-
const dateRange = { startDate: ga4RelativeDate(historyDays), endDate: ga4RelativeDate(0) };
|
|
48600
|
+
const dateRange = options.dateRange ? measurementDateRange(days, options.dateRange, GA_OVERVIEW_HISTORY_DAYS).dateRange : { startDate: ga4RelativeDate(historyDays), endDate: ga4RelativeDate(0) };
|
|
48601
|
+
const rowLimit = options.dateRange ? inclusiveDayCount(dateRange.startDate, dateRange.endDate) : historyDays + 1;
|
|
48596
48602
|
ga4Log("info", "fetch-daily-totals.start", { propertyId, days: historyDays });
|
|
48597
48603
|
const [totalsRes, organicRes] = await batchRunReports(accessToken, propertyId, [
|
|
48598
48604
|
{
|
|
@@ -48610,7 +48616,7 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
|
|
|
48610
48616
|
{ name: GA4_METRICS.userEngagementDuration },
|
|
48611
48617
|
{ name: GA4_METRICS.bounceRate }
|
|
48612
48618
|
],
|
|
48613
|
-
limit:
|
|
48619
|
+
limit: rowLimit
|
|
48614
48620
|
},
|
|
48615
48621
|
{
|
|
48616
48622
|
dateRanges: [dateRange],
|
|
@@ -48622,7 +48628,7 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
|
|
|
48622
48628
|
stringFilter: { matchType: "EXACT", value: "Organic Search" }
|
|
48623
48629
|
}
|
|
48624
48630
|
},
|
|
48625
|
-
limit:
|
|
48631
|
+
limit: rowLimit
|
|
48626
48632
|
}
|
|
48627
48633
|
]);
|
|
48628
48634
|
const organicByDate = /* @__PURE__ */ new Map();
|
|
@@ -48652,18 +48658,17 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
|
|
|
48652
48658
|
const timeZone = reportTimeZone(totalsRes, organicRes) ?? "UTC";
|
|
48653
48659
|
ga4Log("info", "fetch-daily-totals.done", { propertyId, days: historyDays, timeZone, rows: rows.length });
|
|
48654
48660
|
return {
|
|
48655
|
-
startDate: isoDateDaysBeforeInTimeZone(requestedAt, historyDays, timeZone),
|
|
48656
|
-
endDate: isoDateDaysBeforeInTimeZone(requestedAt, 0, timeZone),
|
|
48661
|
+
startDate: options.dateRange?.startDate ?? isoDateDaysBeforeInTimeZone(requestedAt, historyDays, timeZone),
|
|
48662
|
+
endDate: options.dateRange?.endDate ?? isoDateDaysBeforeInTimeZone(requestedAt, 0, timeZone),
|
|
48657
48663
|
rows
|
|
48658
48664
|
};
|
|
48659
48665
|
}
|
|
48660
|
-
var WINDOW_DAYS2 = { "7d": 7, "30d": 30, "90d": 90 };
|
|
48661
48666
|
async function fetchWindowSummary(accessToken, propertyId, windowKey) {
|
|
48662
48667
|
validateAccessToken2(accessToken);
|
|
48663
48668
|
validatePropertyId(propertyId);
|
|
48664
|
-
const days =
|
|
48669
|
+
const days = GA_OVERVIEW_WINDOW_DAYS[windowKey];
|
|
48665
48670
|
if (!days) {
|
|
48666
|
-
throw new GA4ApiError(`Unsupported windowKey "${windowKey}" \u2014 must be 7d, 30d, or
|
|
48671
|
+
throw new GA4ApiError(`Unsupported windowKey "${windowKey}" \u2014 must be 7d, 30d, 90d, or 180d`, 400);
|
|
48667
48672
|
}
|
|
48668
48673
|
const endDate = /* @__PURE__ */ new Date();
|
|
48669
48674
|
const startDate = /* @__PURE__ */ new Date();
|
|
@@ -48793,16 +48798,12 @@ async function fetchWindowMetrics(accessToken, propertyId, windowKey, options =
|
|
|
48793
48798
|
previous: period2(COMPARISON_RANGE_NAMES.previous, ranges.previous)
|
|
48794
48799
|
};
|
|
48795
48800
|
ga4Log("info", "fetch-window-metrics.done", { propertyId, windowKey, timeZone });
|
|
48796
|
-
return result;
|
|
48801
|
+
return { ...result, timeZone };
|
|
48797
48802
|
}
|
|
48798
|
-
async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
48803
|
+
async function fetchAiReferrals(accessToken, propertyId, days, dateRangeOverride) {
|
|
48799
48804
|
validateAccessToken2(accessToken);
|
|
48800
48805
|
validatePropertyId(propertyId);
|
|
48801
|
-
const {
|
|
48802
|
-
const endDate = /* @__PURE__ */ new Date();
|
|
48803
|
-
const startDate = /* @__PURE__ */ new Date();
|
|
48804
|
-
startDate.setDate(startDate.getDate() - syncDays);
|
|
48805
|
-
const dateRange = { startDate: formatDate(startDate), endDate: formatDate(endDate) };
|
|
48806
|
+
const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
|
|
48806
48807
|
ga4Log("info", "fetch-ai-referrals.start", { propertyId, days: syncDays });
|
|
48807
48808
|
const rows = [];
|
|
48808
48809
|
for (const pass of AI_REFERRAL_PASSES) {
|
|
@@ -48900,20 +48901,18 @@ async function fetchAiReferralPass(accessToken, propertyId, dateRange, pass, cha
|
|
|
48900
48901
|
};
|
|
48901
48902
|
});
|
|
48902
48903
|
rows.push(...pageRows);
|
|
48903
|
-
const totalRows = response.rowCount
|
|
48904
|
+
const totalRows = response.rowCount;
|
|
48904
48905
|
offset += pageRows.length;
|
|
48905
|
-
if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
|
|
48906
|
+
if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
|
|
48907
|
+
if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
|
|
48906
48908
|
}
|
|
48907
48909
|
return rows;
|
|
48908
48910
|
}
|
|
48909
48911
|
var SOCIAL_CHANNEL_GROUPS = ["Organic Social", "Paid Social"];
|
|
48910
|
-
async function fetchSocialReferrals(accessToken, propertyId, days) {
|
|
48912
|
+
async function fetchSocialReferrals(accessToken, propertyId, days, dateRangeOverride) {
|
|
48911
48913
|
validateAccessToken2(accessToken);
|
|
48912
48914
|
validatePropertyId(propertyId);
|
|
48913
|
-
const {
|
|
48914
|
-
const endDate = /* @__PURE__ */ new Date();
|
|
48915
|
-
const startDate = /* @__PURE__ */ new Date();
|
|
48916
|
-
startDate.setDate(startDate.getDate() - syncDays);
|
|
48915
|
+
const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
|
|
48917
48916
|
ga4Log("info", "fetch-social-referrals.start", { propertyId, days: syncDays });
|
|
48918
48917
|
const PAGE_SIZE = 1e3;
|
|
48919
48918
|
const rows = [];
|
|
@@ -48921,7 +48920,7 @@ async function fetchSocialReferrals(accessToken, propertyId, days) {
|
|
|
48921
48920
|
let pageCount = 0;
|
|
48922
48921
|
while (pageCount < GA4_MAX_PAGES) {
|
|
48923
48922
|
const request = {
|
|
48924
|
-
dateRanges: [
|
|
48923
|
+
dateRanges: [dateRange],
|
|
48925
48924
|
dimensions: [
|
|
48926
48925
|
{ name: GA4_DIMENSIONS.date },
|
|
48927
48926
|
{ name: GA4_DIMENSIONS.sessionSource },
|
|
@@ -48956,10 +48955,11 @@ async function fetchSocialReferrals(accessToken, propertyId, days) {
|
|
|
48956
48955
|
channelGroup: row.dimensionValues[3].value
|
|
48957
48956
|
}));
|
|
48958
48957
|
rows.push(...pageRows);
|
|
48959
|
-
const totalRows = response.rowCount
|
|
48958
|
+
const totalRows = response.rowCount;
|
|
48960
48959
|
offset += pageRows.length;
|
|
48961
48960
|
pageCount += 1;
|
|
48962
|
-
if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
|
|
48961
|
+
if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
|
|
48962
|
+
if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
|
|
48963
48963
|
}
|
|
48964
48964
|
for (const row of rows) {
|
|
48965
48965
|
row.date = compactDateToIso(row.date);
|
|
@@ -49012,10 +49012,14 @@ function isSearchConsoleUnavailable(error) {
|
|
|
49012
49012
|
}
|
|
49013
49013
|
async function fetchSearchLandingWindow(accessToken, propertyId, window2, options) {
|
|
49014
49014
|
const days = GA_SEARCH_LANDING_WINDOW_DAYS[window2];
|
|
49015
|
+
const explicitRange = options.timeZone === void 0 ? null : {
|
|
49016
|
+
startDate: isoDateDaysBeforeInTimeZone(options.requestedAt, days, options.timeZone),
|
|
49017
|
+
endDate: isoDateDaysBeforeInTimeZone(options.requestedAt, 1, options.timeZone)
|
|
49018
|
+
};
|
|
49015
49019
|
const { rows: rawRows, firstPage, reportRowCount, capped } = await fetchPagedReportWithTotals(accessToken, propertyId, {
|
|
49016
|
-
//
|
|
49017
|
-
//
|
|
49018
|
-
dateRanges: [{ startDate: `${days}daysAgo`, endDate: "yesterday" }],
|
|
49020
|
+
// A known reporting zone pins the planner's calendar bounds across I/O.
|
|
49021
|
+
// Otherwise GA4 resolves the relative range in its own reporting zone.
|
|
49022
|
+
dateRanges: [explicitRange ?? { startDate: `${days}daysAgo`, endDate: "yesterday" }],
|
|
49019
49023
|
dimensions: [{ name: GA4_DIMENSIONS.landingPagePlusQueryString }],
|
|
49020
49024
|
metrics: SEARCH_LANDING_METRIC_NAMES.map((name) => ({ name })),
|
|
49021
49025
|
metricAggregations: ["TOTAL"],
|
|
@@ -49047,8 +49051,8 @@ async function fetchSearchLandingWindow(accessToken, propertyId, window2, option
|
|
|
49047
49051
|
const zone = timeZone ?? "UTC";
|
|
49048
49052
|
return {
|
|
49049
49053
|
window: window2,
|
|
49050
|
-
periodStart: isoDateDaysBeforeInTimeZone(options.requestedAt, days, zone),
|
|
49051
|
-
periodEnd: isoDateDaysBeforeInTimeZone(options.requestedAt, 1, zone),
|
|
49054
|
+
periodStart: explicitRange?.startDate ?? isoDateDaysBeforeInTimeZone(options.requestedAt, days, zone),
|
|
49055
|
+
periodEnd: explicitRange?.endDate ?? isoDateDaysBeforeInTimeZone(options.requestedAt, 1, zone),
|
|
49052
49056
|
timeZone,
|
|
49053
49057
|
total,
|
|
49054
49058
|
rows,
|
|
@@ -49062,13 +49066,17 @@ async function fetchSearchLandingWindow(accessToken, propertyId, window2, option
|
|
|
49062
49066
|
async function fetchSearchLandingPages(accessToken, propertyId, options = {}) {
|
|
49063
49067
|
validateAccessToken2(accessToken);
|
|
49064
49068
|
validatePropertyId(propertyId);
|
|
49069
|
+
const requestedWindows = options.windows ?? GA_SEARCH_LANDING_WINDOWS;
|
|
49070
|
+
if (requestedWindows.length === 0 || new Set(requestedWindows).size !== requestedWindows.length || requestedWindows.some((window2) => !GA_SEARCH_LANDING_WINDOWS.includes(window2))) {
|
|
49071
|
+
throw new GA4ApiError("Search landing windows must be a non-empty selection of unique supported windows", 400);
|
|
49072
|
+
}
|
|
49065
49073
|
const requestedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
49066
49074
|
const pageSize = options.pageSize ?? GA4_SEARCH_LANDING_PAGE_SIZE;
|
|
49067
49075
|
const maxRows = options.maxRows ?? GA4_SEARCH_LANDING_MAX_ROWS;
|
|
49068
49076
|
ga4Log("info", "fetch-search-landing.start", { propertyId });
|
|
49069
49077
|
let windows;
|
|
49070
49078
|
try {
|
|
49071
|
-
windows = await Promise.all(
|
|
49079
|
+
windows = await Promise.all(requestedWindows.map((window2) => fetchSearchLandingWindow(accessToken, propertyId, window2, { requestedAt, pageSize, maxRows, timeZone: options.timeZone })));
|
|
49072
49080
|
} catch (error) {
|
|
49073
49081
|
if (!isSearchConsoleUnavailable(error)) throw error;
|
|
49074
49082
|
ga4Log("info", "fetch-search-landing.unavailable", { propertyId, httpStatus: 400 });
|
|
@@ -59608,7 +59616,7 @@ async function cdpRoutes(app, opts) {
|
|
|
59608
59616
|
|
|
59609
59617
|
// ../api-routes/src/ga.ts
|
|
59610
59618
|
import crypto51 from "crypto";
|
|
59611
|
-
import { eq as
|
|
59619
|
+
import { eq as eq86, desc as desc38, and as and74, sql as sql37 } from "drizzle-orm";
|
|
59612
59620
|
|
|
59613
59621
|
// ../api-routes/src/ga-session-history.ts
|
|
59614
59622
|
function buildSessionHistory(landingPageDays, dailyTotals) {
|
|
@@ -59628,7 +59636,7 @@ function buildSessionHistory(landingPageDays, dailyTotals) {
|
|
|
59628
59636
|
// ../api-routes/src/ga-overview.ts
|
|
59629
59637
|
function parseGaOverviewWindow(value) {
|
|
59630
59638
|
if (value === void 0 || value === "") return "30d";
|
|
59631
|
-
if (!isGaOverviewWindow(value)) throw validationError(`Invalid window "${value}": the GA4 overview supports 7d, 30d or
|
|
59639
|
+
if (!isGaOverviewWindow(value)) throw validationError(`Invalid window "${value}": the GA4 overview supports 7d, 30d, 90d or 180d.`);
|
|
59632
59640
|
return value;
|
|
59633
59641
|
}
|
|
59634
59642
|
var NO_VALUES = {
|
|
@@ -59771,13 +59779,93 @@ function findBiggestMover(current, prior) {
|
|
|
59771
59779
|
return mover;
|
|
59772
59780
|
}
|
|
59773
59781
|
|
|
59782
|
+
// ../api-routes/src/ga-sync-coverage.ts
|
|
59783
|
+
import { and as and72, eq as eq84, lt as lt17 } from "drizzle-orm";
|
|
59784
|
+
function planGaSyncCoverage(db, input, now) {
|
|
59785
|
+
const definition2 = input.definition ?? "v1";
|
|
59786
|
+
const refreshStart = shiftIsoCalendarDate(input.requested.endDate, -13);
|
|
59787
|
+
const reconcileAfter = new Date(Date.parse(now) - 30 * 864e5).toISOString();
|
|
59788
|
+
const covered = new Set(db.select().from(gaSyncCoverage).where(and72(
|
|
59789
|
+
eq84(gaSyncCoverage.projectId, input.projectId),
|
|
59790
|
+
eq84(gaSyncCoverage.propertyId, input.propertyId),
|
|
59791
|
+
eq84(gaSyncCoverage.component, input.component),
|
|
59792
|
+
eq84(gaSyncCoverage.definition, definition2)
|
|
59793
|
+
)).all().filter((row) => row.settled && row.syncedAt > reconcileAfter).map((row) => row.date));
|
|
59794
|
+
const missing = calendarDateRange(input.requested.startDate, input.requested.endDate).filter((date) => date >= refreshStart || !covered.has(date));
|
|
59795
|
+
return {
|
|
59796
|
+
...input,
|
|
59797
|
+
definition: definition2,
|
|
59798
|
+
refreshStart,
|
|
59799
|
+
ranges: groupIsoDateRanges(missing).map((range) => ({ startDate: range.start, endDate: range.end }))
|
|
59800
|
+
};
|
|
59801
|
+
}
|
|
59802
|
+
function saveGaSyncCoverage(tx, plan, ranges, syncedAt, syncRunId) {
|
|
59803
|
+
for (const range of ranges) {
|
|
59804
|
+
for (const date of calendarDateRange(range.startDate, range.endDate)) {
|
|
59805
|
+
const values = {
|
|
59806
|
+
projectId: plan.projectId,
|
|
59807
|
+
propertyId: plan.propertyId,
|
|
59808
|
+
component: plan.component,
|
|
59809
|
+
definition: plan.definition,
|
|
59810
|
+
date,
|
|
59811
|
+
settled: date < plan.refreshStart,
|
|
59812
|
+
syncedAt,
|
|
59813
|
+
syncRunId
|
|
59814
|
+
};
|
|
59815
|
+
tx.insert(gaSyncCoverage).values(values).onConflictDoUpdate({
|
|
59816
|
+
target: [gaSyncCoverage.projectId, gaSyncCoverage.component, gaSyncCoverage.date],
|
|
59817
|
+
set: values
|
|
59818
|
+
}).run();
|
|
59819
|
+
}
|
|
59820
|
+
}
|
|
59821
|
+
tx.delete(gaSyncCoverage).where(and72(
|
|
59822
|
+
eq84(gaSyncCoverage.projectId, plan.projectId),
|
|
59823
|
+
lt17(gaSyncCoverage.date, shiftIsoCalendarDate(plan.requested.endDate, -362))
|
|
59824
|
+
)).run();
|
|
59825
|
+
}
|
|
59826
|
+
async function fetchGaSyncRanges(plan, fetchRange) {
|
|
59827
|
+
const results = [];
|
|
59828
|
+
for (const range of plan.ranges) {
|
|
59829
|
+
results.push({ range, report: await fetchRange(range) });
|
|
59830
|
+
}
|
|
59831
|
+
return results;
|
|
59832
|
+
}
|
|
59833
|
+
|
|
59774
59834
|
// ../api-routes/src/ga-search-landing-pages.ts
|
|
59775
59835
|
import crypto50 from "crypto";
|
|
59776
|
-
import { and as
|
|
59836
|
+
import { and as and73, asc as asc21, count as count5, desc as desc37, eq as eq85, gte as gte23, lte as lte23, sql as sql36 } from "drizzle-orm";
|
|
59837
|
+
function searchLandingWindowsToFetch(db, projectId, propertyId, timeZone, requestedAt) {
|
|
59838
|
+
const state = db.select().from(gaMeasurementSyncStates).where(eq85(gaMeasurementSyncStates.projectId, projectId)).get();
|
|
59839
|
+
if (!timeZone || state?.searchLandingStatus !== "ready") return [...GA_SEARCH_LANDING_WINDOWS];
|
|
59840
|
+
const stored = db.select().from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).all();
|
|
59841
|
+
const now = Date.parse(requestedAt);
|
|
59842
|
+
const end = isoDateDaysBeforeInTimeZone(requestedAt, 1, timeZone);
|
|
59843
|
+
return GA_SEARCH_LANDING_WINDOWS.filter((window2) => {
|
|
59844
|
+
const snapshot = stored.find((row) => row.windowKey === window2);
|
|
59845
|
+
if (!snapshot) return true;
|
|
59846
|
+
const age = now - Date.parse(snapshot.syncedAt);
|
|
59847
|
+
return snapshot.propertyId !== propertyId || snapshot.reportDefinition !== GA4_SEARCH_LANDING_REPORT_DEFINITION || snapshot.timeZone !== timeZone || snapshot.periodStart !== isoDateDaysBeforeInTimeZone(requestedAt, GA_SEARCH_LANDING_WINDOW_DAYS[window2], timeZone) || snapshot.periodEnd !== end || !(age >= 0 && age < 60 * 6e4);
|
|
59848
|
+
});
|
|
59849
|
+
}
|
|
59777
59850
|
function persistSearchLandingSnapshot(db, input) {
|
|
59778
59851
|
db.transaction((tx) => {
|
|
59779
|
-
|
|
59852
|
+
if (input.reuse.windows.length > 0) {
|
|
59853
|
+
const missing = searchLandingWindowsToFetch(tx, input.projectId, input.propertyId, input.reuse.timeZone, input.reuse.requestedAt);
|
|
59854
|
+
if (missing.some((window2) => input.reuse.windows.includes(window2))) {
|
|
59855
|
+
throw new Error("Search landing snapshots changed during sync; retry to refresh the missing windows");
|
|
59856
|
+
}
|
|
59857
|
+
}
|
|
59858
|
+
const otherProperty = tx.select({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, input.projectId)).all().some((row) => row.propertyId !== input.propertyId);
|
|
59859
|
+
if (otherProperty) deleteSearchLandingRows(tx, input.projectId);
|
|
59780
59860
|
for (const window2 of input.windows) {
|
|
59861
|
+
tx.delete(gaSearchLandingPages).where(and73(
|
|
59862
|
+
eq85(gaSearchLandingPages.projectId, input.projectId),
|
|
59863
|
+
eq85(gaSearchLandingPages.windowKey, window2.window)
|
|
59864
|
+
)).run();
|
|
59865
|
+
tx.delete(gaSearchLandingWindows).where(and73(
|
|
59866
|
+
eq85(gaSearchLandingWindows.projectId, input.projectId),
|
|
59867
|
+
eq85(gaSearchLandingWindows.windowKey, window2.window)
|
|
59868
|
+
)).run();
|
|
59781
59869
|
tx.insert(gaSearchLandingWindows).values({
|
|
59782
59870
|
id: crypto50.randomUUID(),
|
|
59783
59871
|
projectId: input.projectId,
|
|
@@ -59786,6 +59874,7 @@ function persistSearchLandingSnapshot(db, input) {
|
|
|
59786
59874
|
periodStart: window2.periodStart,
|
|
59787
59875
|
periodEnd: window2.periodEnd,
|
|
59788
59876
|
timeZone: window2.timeZone,
|
|
59877
|
+
reportDefinition: GA4_SEARCH_LANDING_REPORT_DEFINITION,
|
|
59789
59878
|
totalClicks: window2.total.clicks,
|
|
59790
59879
|
totalImpressions: window2.total.impressions,
|
|
59791
59880
|
totalCtr: window2.total.ctr,
|
|
@@ -59819,7 +59908,7 @@ function persistSearchLandingSnapshot(db, input) {
|
|
|
59819
59908
|
upsertSearchLandingState(tx, input.projectId, {
|
|
59820
59909
|
searchLandingStatus: "ready",
|
|
59821
59910
|
searchLandingError: null,
|
|
59822
|
-
searchLandingSyncedAt: input.syncedAt,
|
|
59911
|
+
...input.windows.length > 0 ? { searchLandingSyncedAt: input.syncedAt } : {},
|
|
59823
59912
|
searchLandingAttemptedAt: input.syncedAt
|
|
59824
59913
|
}, input.syncedAt);
|
|
59825
59914
|
});
|
|
@@ -59837,7 +59926,7 @@ function persistSearchLandingSnapshot(db, input) {
|
|
|
59837
59926
|
}
|
|
59838
59927
|
function persistSearchLandingFailure(db, projectId, propertyId, status, message, attemptedAt) {
|
|
59839
59928
|
db.transaction((tx) => {
|
|
59840
|
-
const storedProperties = tx.selectDistinct({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(
|
|
59929
|
+
const storedProperties = tx.selectDistinct({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).all();
|
|
59841
59930
|
const otherProperty = storedProperties.some((stored) => stored.propertyId !== propertyId);
|
|
59842
59931
|
if (otherProperty) deleteSearchLandingRows(tx, projectId);
|
|
59843
59932
|
upsertSearchLandingState(tx, projectId, {
|
|
@@ -59849,8 +59938,8 @@ function persistSearchLandingFailure(db, projectId, propertyId, status, message,
|
|
|
59849
59938
|
});
|
|
59850
59939
|
}
|
|
59851
59940
|
function deleteSearchLandingRows(db, projectId) {
|
|
59852
|
-
db.delete(gaSearchLandingPages).where(
|
|
59853
|
-
db.delete(gaSearchLandingWindows).where(
|
|
59941
|
+
db.delete(gaSearchLandingPages).where(eq85(gaSearchLandingPages.projectId, projectId)).run();
|
|
59942
|
+
db.delete(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).run();
|
|
59854
59943
|
}
|
|
59855
59944
|
function upsertSearchLandingState(db, projectId, fields, updatedAt) {
|
|
59856
59945
|
db.insert(gaMeasurementSyncStates).values({ projectId, ...fields, updatedAt }).onConflictDoUpdate({
|
|
@@ -59882,11 +59971,11 @@ function buildGaSearchLandingPages(db, projectName, query2, connections) {
|
|
|
59882
59971
|
throw validationError('"offset" must be a non-negative integer');
|
|
59883
59972
|
}
|
|
59884
59973
|
const project = resolveProject(db, projectName);
|
|
59885
|
-
const storedWindow = db.select().from(gaSearchLandingWindows).where(
|
|
59974
|
+
const storedWindow = db.select().from(gaSearchLandingWindows).where(and73(eq85(gaSearchLandingWindows.projectId, project.id), eq85(gaSearchLandingWindows.windowKey, window2))).get();
|
|
59886
59975
|
const otherProperty = storedWindow !== void 0 && storedWindow.propertyId !== resolveCurrentGa4PropertyId(connections, project.name, project.canonicalDomain);
|
|
59887
59976
|
const stored = otherProperty ? void 0 : storedWindow;
|
|
59888
|
-
const state = otherProperty ? void 0 : db.select().from(gaMeasurementSyncStates).where(
|
|
59889
|
-
const pageScope =
|
|
59977
|
+
const state = otherProperty ? void 0 : db.select().from(gaMeasurementSyncStates).where(eq85(gaMeasurementSyncStates.projectId, project.id)).get();
|
|
59978
|
+
const pageScope = and73(eq85(gaSearchLandingPages.projectId, project.id), eq85(gaSearchLandingPages.windowKey, window2));
|
|
59890
59979
|
const totalRows = otherProperty ? 0 : db.select({ value: count5() }).from(gaSearchLandingPages).where(pageScope).get()?.value ?? 0;
|
|
59891
59980
|
const rows = otherProperty ? [] : db.select().from(gaSearchLandingPages).where(pageScope).orderBy(desc37(gaSearchLandingPages.clicks), desc37(gaSearchLandingPages.impressions), asc21(gaSearchLandingPages.landingPage)).limit(limit).offset(offset).all();
|
|
59892
59981
|
const total = stored ? toWireMetrics({
|
|
@@ -59926,7 +60015,7 @@ function searchConsoleLinkFor(db, projectId, total, windowStart, windowEnd) {
|
|
|
59926
60015
|
const stored = db.select({
|
|
59927
60016
|
days: count5(),
|
|
59928
60017
|
impressions: sql36`sum(${gscDailyTotals.impressions})`
|
|
59929
|
-
}).from(gscDailyTotals).where(
|
|
60018
|
+
}).from(gscDailyTotals).where(and73(eq85(gscDailyTotals.projectId, projectId), gte23(gscDailyTotals.date, windowStart), lte23(gscDailyTotals.date, windowEnd))).get();
|
|
59930
60019
|
const searchConsoleImpressions = stored && stored.days > 0 ? Number(stored.impressions ?? 0) : null;
|
|
59931
60020
|
if (gaSearchLandingHasSearchData(total)) return { state: "has-data", searchConsoleImpressions };
|
|
59932
60021
|
if (searchConsoleImpressions === null) return { state: "unknown", searchConsoleImpressions };
|
|
@@ -59951,9 +60040,10 @@ function withSessionShares(rows) {
|
|
|
59951
60040
|
}
|
|
59952
60041
|
var DAY_MS4 = 864e5;
|
|
59953
60042
|
var GA_TREND_FILLING_DAYS = 2;
|
|
60043
|
+
var gaFillingFrom = (now) => new Date(Date.parse(now.toISOString().slice(0, 10)) - (GA_TREND_FILLING_DAYS - 1) * DAY_MS4).toISOString().slice(0, 10);
|
|
59954
60044
|
function aiReferralDailyTrend(days, measuredDates, firstAiDate, now) {
|
|
59955
60045
|
if (!firstAiDate) return null;
|
|
59956
|
-
const fillingFrom =
|
|
60046
|
+
const fillingFrom = gaFillingFrom(now);
|
|
59957
60047
|
const usable = [...new Set(measuredDates)].filter((date) => date >= firstAiDate && date < fillingFrom).sort();
|
|
59958
60048
|
if (usable.length < 2) return null;
|
|
59959
60049
|
const sessionsByDate = new Map(days.map((day) => [day.date, day.sessions]));
|
|
@@ -59964,6 +60054,49 @@ function aiReferralDailyTrend(days, measuredDates, firstAiDate, now) {
|
|
|
59964
60054
|
const fit = linearTrend(axis.map((date) => measured.has(date) ? sessionsByDate.get(date) ?? 0 : null));
|
|
59965
60055
|
return fit ? { ...fit, startDate: axis[fit.startIndex], endDate: axis[fit.endIndex] } : null;
|
|
59966
60056
|
}
|
|
60057
|
+
var addMonths = (month, n) => {
|
|
60058
|
+
const [y, m] = month.split("-").map(Number);
|
|
60059
|
+
return new Date(Date.UTC(y, m - 1 + n, 1)).toISOString().slice(0, 7);
|
|
60060
|
+
};
|
|
60061
|
+
var lastDayOf = (month) => new Date(Date.parse(`${addMonths(month, 1)}-01T00:00:00.000Z`) - DAY_MS4).toISOString().slice(0, 10);
|
|
60062
|
+
function aiReferralMonthlySeries(input) {
|
|
60063
|
+
const today = input.now.toISOString().slice(0, 10);
|
|
60064
|
+
const fillingFrom = gaFillingFrom(input.now);
|
|
60065
|
+
const firstDay = input.range.startDate ?? [...input.days.map((day) => day.date), ...input.measuredDates].sort()[0];
|
|
60066
|
+
if (!firstDay) return { months: [], monthlyTrend: null };
|
|
60067
|
+
const lastDay = input.range.endDate && input.range.endDate < today ? input.range.endDate : today;
|
|
60068
|
+
const labels = new Map(input.byEngine.series.map((s) => [s.key, s.label]));
|
|
60069
|
+
const measured = new Set(input.measuredDates);
|
|
60070
|
+
const months = [];
|
|
60071
|
+
for (let month = firstDay.slice(0, 7); month <= lastDay.slice(0, 7); month = addMonths(month, 1)) {
|
|
60072
|
+
const inMonth = (date) => date.startsWith(month);
|
|
60073
|
+
const monthStart = `${month}-01`;
|
|
60074
|
+
const monthEnd = lastDayOf(month);
|
|
60075
|
+
const monthDates = calendarDateRange(monthStart, monthEnd);
|
|
60076
|
+
const days = input.days.filter((day) => inMonth(day.date));
|
|
60077
|
+
const reported = days.length > 0 || monthDates.some((date) => measured.has(date));
|
|
60078
|
+
const engines = /* @__PURE__ */ new Map();
|
|
60079
|
+
for (const day of input.byEngine.days) {
|
|
60080
|
+
if (!inMonth(day.date)) continue;
|
|
60081
|
+
for (const [key, value] of Object.entries(day.values)) engines.set(key, (engines.get(key) ?? 0) + value);
|
|
60082
|
+
}
|
|
60083
|
+
months.push({
|
|
60084
|
+
month,
|
|
60085
|
+
sessions: reported ? days.reduce((sum, day) => sum + day.sessions, 0) : null,
|
|
60086
|
+
complete: reported && monthStart >= firstDay && monthEnd <= lastDay && monthEnd < fillingFrom && monthDates.every((date) => measured.has(date)),
|
|
60087
|
+
engines: [...engines].filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1]).map(([key, sessions]) => ({ key, label: labels.get(key) ?? key, sessions }))
|
|
60088
|
+
});
|
|
60089
|
+
}
|
|
60090
|
+
const fromMonth = input.firstAiDate?.slice(0, 7);
|
|
60091
|
+
const fit = fromMonth ? linearTrend(months.map((m) => m.complete && m.month >= fromMonth ? m.sessions : null)) : null;
|
|
60092
|
+
const monthlyTrend = fit ? {
|
|
60093
|
+
...fit,
|
|
60094
|
+
startMonth: months[fit.startIndex].month,
|
|
60095
|
+
endMonth: months[fit.endIndex].month,
|
|
60096
|
+
changePct: fit.start > 0 ? deltaPercent(fit.end, fit.start) : null
|
|
60097
|
+
} : null;
|
|
60098
|
+
return { months, monthlyTrend };
|
|
60099
|
+
}
|
|
59967
60100
|
function dateRangeConditions(column, range) {
|
|
59968
60101
|
const conditions = [];
|
|
59969
60102
|
if (range.startDate) conditions.push(sql37`${column} >= ${range.startDate}`);
|
|
@@ -60025,8 +60158,8 @@ function keepWinningDimensionRows(rows, groupKey) {
|
|
|
60025
60158
|
function persistAcquisitionMeasurement(db, input) {
|
|
60026
60159
|
const { startDate, endDate } = input.report;
|
|
60027
60160
|
db.transaction((tx) => {
|
|
60028
|
-
tx.delete(gaAcquisitionDaily).where(
|
|
60029
|
-
|
|
60161
|
+
tx.delete(gaAcquisitionDaily).where(and74(
|
|
60162
|
+
eq86(gaAcquisitionDaily.projectId, input.projectId),
|
|
60030
60163
|
sql37`${gaAcquisitionDaily.date} >= ${startDate}`,
|
|
60031
60164
|
sql37`${gaAcquisitionDaily.date} <= ${endDate}`
|
|
60032
60165
|
)).run();
|
|
@@ -60067,8 +60200,8 @@ function persistAcquisitionMeasurement(db, input) {
|
|
|
60067
60200
|
function persistLeadMeasurement(db, input) {
|
|
60068
60201
|
const { startDate, endDate } = input.report;
|
|
60069
60202
|
db.transaction((tx) => {
|
|
60070
|
-
tx.delete(gaLeadEventsDaily).where(
|
|
60071
|
-
|
|
60203
|
+
tx.delete(gaLeadEventsDaily).where(and74(
|
|
60204
|
+
eq86(gaLeadEventsDaily.projectId, input.projectId),
|
|
60072
60205
|
sql37`${gaLeadEventsDaily.date} >= ${startDate}`,
|
|
60073
60206
|
sql37`${gaLeadEventsDaily.date} <= ${endDate}`
|
|
60074
60207
|
)).run();
|
|
@@ -60114,7 +60247,8 @@ function persistLeadMeasurement(db, input) {
|
|
|
60114
60247
|
}
|
|
60115
60248
|
function resetLeadMeasurement(db, projectId, updatedAt) {
|
|
60116
60249
|
db.transaction((tx) => {
|
|
60117
|
-
tx.delete(gaLeadEventsDaily).where(
|
|
60250
|
+
tx.delete(gaLeadEventsDaily).where(eq86(gaLeadEventsDaily.projectId, projectId)).run();
|
|
60251
|
+
tx.delete(gaSyncCoverage).where(and74(eq86(gaSyncCoverage.projectId, projectId), eq86(gaSyncCoverage.component, "leads"))).run();
|
|
60118
60252
|
tx.insert(gaMeasurementSyncStates).values({ projectId, leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }).onConflictDoUpdate({
|
|
60119
60253
|
target: gaMeasurementSyncStates.projectId,
|
|
60120
60254
|
set: { leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }
|
|
@@ -60456,7 +60590,7 @@ async function ga4Routes(app, opts) {
|
|
|
60456
60590
|
if (!connected2) {
|
|
60457
60591
|
return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
|
|
60458
60592
|
}
|
|
60459
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
60593
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
60460
60594
|
return {
|
|
60461
60595
|
connected: true,
|
|
60462
60596
|
propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
|
|
@@ -60479,7 +60613,7 @@ async function ga4Routes(app, opts) {
|
|
|
60479
60613
|
const syncAi = !only || only === "ai";
|
|
60480
60614
|
const syncSocial = !only || only === "social";
|
|
60481
60615
|
const syncSearchLanding = !only || only === "search-landing";
|
|
60482
|
-
const measurementState = app.db.select().from(gaMeasurementSyncStates).where(
|
|
60616
|
+
const measurementState = app.db.select().from(gaMeasurementSyncStates).where(eq86(gaMeasurementSyncStates.projectId, project.id)).get();
|
|
60483
60617
|
const acquisitionDays = measurementState?.acquisitionSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
|
|
60484
60618
|
let leadDays = GA4_MAX_SYNC_DAYS;
|
|
60485
60619
|
let leadEventNames = [];
|
|
@@ -60516,47 +60650,72 @@ async function ga4Routes(app, opts) {
|
|
|
60516
60650
|
let rows = [];
|
|
60517
60651
|
let aiReferrals = [];
|
|
60518
60652
|
let socialReferrals = [];
|
|
60519
|
-
const WINDOW_KEYS =
|
|
60520
|
-
const
|
|
60653
|
+
const WINDOW_KEYS = GA_OVERVIEW_WINDOWS;
|
|
60654
|
+
const [summary3, windowSummaries, windowMetrics] = await Promise.all([
|
|
60521
60655
|
fetchAggregateSummary(accessToken, propertyId, days),
|
|
60522
|
-
|
|
60523
|
-
|
|
60524
|
-
|
|
60525
|
-
|
|
60526
|
-
|
|
60527
|
-
|
|
60528
|
-
|
|
60656
|
+
Promise.all(WINDOW_KEYS.map((w) => fetchWindowSummary(accessToken, propertyId, w))),
|
|
60657
|
+
Promise.all(WINDOW_KEYS.map((w) => fetchWindowMetrics(accessToken, propertyId, w)))
|
|
60658
|
+
]);
|
|
60659
|
+
const requested = { startDate: summary3.periodStart, endDate: summary3.periodEnd };
|
|
60660
|
+
const definition2 = JSON.stringify(["v1", windowMetrics[0].timeZone ?? "UTC"]);
|
|
60661
|
+
const planInput = { projectId: project.id, propertyId, requested, definition: definition2 };
|
|
60662
|
+
const trafficPlan = planGaSyncCoverage(app.db, { ...planInput, component: "traffic" }, startedAt);
|
|
60663
|
+
const aiPlan = planGaSyncCoverage(app.db, { ...planInput, component: "ai" }, startedAt);
|
|
60664
|
+
const socialPlan = planGaSyncCoverage(app.db, { ...planInput, component: "social" }, startedAt);
|
|
60665
|
+
const longestWindow = windowMetrics.at(-1);
|
|
60666
|
+
const dailyPlan = planGaSyncCoverage(app.db, {
|
|
60667
|
+
...planInput,
|
|
60668
|
+
component: "daily",
|
|
60669
|
+
requested: { startDate: longestWindow.previous.startDate, endDate: shiftIsoCalendarDate(longestWindow.current.endDate, 1) }
|
|
60670
|
+
}, startedAt);
|
|
60671
|
+
const acquisitionPlan = planGaSyncCoverage(app.db, {
|
|
60672
|
+
...planInput,
|
|
60673
|
+
component: "acquisition",
|
|
60674
|
+
requested: { startDate: shiftIsoCalendarDate(requested.endDate, -acquisitionDays), endDate: requested.endDate }
|
|
60675
|
+
}, startedAt);
|
|
60676
|
+
const leadPlan = planGaSyncCoverage(app.db, {
|
|
60677
|
+
...planInput,
|
|
60678
|
+
component: "leads",
|
|
60679
|
+
definition: JSON.stringify([definition2, ...leadEventNames]),
|
|
60680
|
+
requested: { startDate: shiftIsoCalendarDate(requested.endDate, -leadDays), endDate: requested.endDate }
|
|
60681
|
+
}, startedAt);
|
|
60682
|
+
const searchRequestedAt = /* @__PURE__ */ new Date();
|
|
60683
|
+
const searchTimeZone = windowMetrics[0].timeZone ?? null;
|
|
60684
|
+
const searchWindows = syncSearchLanding ? searchLandingWindowsToFetch(app.db, project.id, propertyId, searchTimeZone, searchRequestedAt.toISOString()) : [];
|
|
60529
60685
|
const measurementFetch = Promise.allSettled([
|
|
60530
|
-
fetchAcquisitionByChannel(accessToken, propertyId, acquisitionDays),
|
|
60531
|
-
leadEventNames.length > 0 ? fetchLeadEvents(accessToken, propertyId, leadEventNames, leadDays) : Promise.resolve(null),
|
|
60532
|
-
|
|
60533
|
-
|
|
60686
|
+
fetchGaSyncRanges(acquisitionPlan, (range) => fetchAcquisitionByChannel(accessToken, propertyId, acquisitionDays, { dateRange: range })),
|
|
60687
|
+
leadEventNames.length > 0 ? fetchGaSyncRanges(leadPlan, (range) => fetchLeadEvents(accessToken, propertyId, leadEventNames, leadDays, range)) : Promise.resolve(null),
|
|
60688
|
+
syncSearchLanding ? searchWindows.length > 0 ? fetchSearchLandingPages(accessToken, propertyId, {
|
|
60689
|
+
windows: searchWindows,
|
|
60690
|
+
now: searchRequestedAt,
|
|
60691
|
+
timeZone: searchTimeZone ?? void 0
|
|
60692
|
+
}) : Promise.resolve({ status: "ready", windows: [] }) : Promise.resolve(null)
|
|
60534
60693
|
]);
|
|
60535
|
-
const
|
|
60536
|
-
|
|
60537
|
-
|
|
60538
|
-
|
|
60539
|
-
|
|
60540
|
-
|
|
60541
|
-
|
|
60542
|
-
|
|
60543
|
-
|
|
60544
|
-
|
|
60545
|
-
aiReferrals = results[idx++];
|
|
60546
|
-
}
|
|
60547
|
-
if (syncSocial) {
|
|
60548
|
-
socialReferrals = results[idx++];
|
|
60549
|
-
}
|
|
60694
|
+
const [dailyReports, trafficReports, aiReports, socialReports] = await Promise.all([
|
|
60695
|
+
fetchGaSyncRanges(dailyPlan, (range) => fetchDailyTotals(accessToken, propertyId, void 0, { dateRange: range })),
|
|
60696
|
+
fetchGaSyncRanges(trafficPlan, (range) => fetchTrafficByLandingPage(accessToken, propertyId, days, range)),
|
|
60697
|
+
syncAi ? fetchGaSyncRanges(aiPlan, (range) => fetchAiReferrals(accessToken, propertyId, days, range)) : Promise.resolve([]),
|
|
60698
|
+
syncSocial ? fetchGaSyncRanges(socialPlan, (range) => fetchSocialReferrals(accessToken, propertyId, days, range)) : Promise.resolve([])
|
|
60699
|
+
]);
|
|
60700
|
+
rows = trafficReports.flatMap((result) => result.report);
|
|
60701
|
+
aiReferrals = aiReports.flatMap((result) => result.report);
|
|
60702
|
+
socialReferrals = socialReports.flatMap((result) => result.report);
|
|
60703
|
+
const dailyTotals = { rows: dailyReports.flatMap((result) => result.report.rows) };
|
|
60550
60704
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60705
|
+
if (resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain) !== propertyId) {
|
|
60706
|
+
throw validationError("GA4 property changed during sync; retry against the current connection");
|
|
60707
|
+
}
|
|
60551
60708
|
app.db.transaction((tx) => {
|
|
60552
60709
|
if (syncTraffic) {
|
|
60553
|
-
|
|
60554
|
-
|
|
60555
|
-
|
|
60556
|
-
|
|
60557
|
-
|
|
60558
|
-
|
|
60559
|
-
|
|
60710
|
+
for (const { range } of trafficReports) {
|
|
60711
|
+
tx.delete(gaTrafficSnapshots).where(
|
|
60712
|
+
and74(
|
|
60713
|
+
eq86(gaTrafficSnapshots.projectId, project.id),
|
|
60714
|
+
sql37`${gaTrafficSnapshots.date} >= ${range.startDate}`,
|
|
60715
|
+
sql37`${gaTrafficSnapshots.date} <= ${range.endDate}`
|
|
60716
|
+
)
|
|
60717
|
+
).run();
|
|
60718
|
+
}
|
|
60560
60719
|
for (const row of rows) {
|
|
60561
60720
|
tx.insert(gaTrafficSnapshots).values({
|
|
60562
60721
|
id: crypto51.randomUUID(),
|
|
@@ -60573,13 +60732,15 @@ async function ga4Routes(app, opts) {
|
|
|
60573
60732
|
}).run();
|
|
60574
60733
|
}
|
|
60575
60734
|
}
|
|
60576
|
-
|
|
60577
|
-
|
|
60578
|
-
|
|
60579
|
-
|
|
60580
|
-
|
|
60581
|
-
|
|
60582
|
-
|
|
60735
|
+
for (const { report } of dailyReports) {
|
|
60736
|
+
tx.delete(gaDailyTotals).where(
|
|
60737
|
+
and74(
|
|
60738
|
+
eq86(gaDailyTotals.projectId, project.id),
|
|
60739
|
+
sql37`${gaDailyTotals.date} >= ${report.startDate}`,
|
|
60740
|
+
sql37`${gaDailyTotals.date} <= ${report.endDate}`
|
|
60741
|
+
)
|
|
60742
|
+
).run();
|
|
60743
|
+
}
|
|
60583
60744
|
for (const row of dailyTotals.rows) {
|
|
60584
60745
|
tx.insert(gaDailyTotals).values({
|
|
60585
60746
|
id: crypto51.randomUUID(),
|
|
@@ -60605,7 +60766,7 @@ async function ga4Routes(app, opts) {
|
|
|
60605
60766
|
createdAt: now
|
|
60606
60767
|
}).run();
|
|
60607
60768
|
}
|
|
60608
|
-
tx.delete(gaWindowMetrics).where(
|
|
60769
|
+
tx.delete(gaWindowMetrics).where(eq86(gaWindowMetrics.projectId, project.id)).run();
|
|
60609
60770
|
for (const windowMetric of windowMetrics) {
|
|
60610
60771
|
for (const [period2, totals] of [
|
|
60611
60772
|
[GaOverviewPeriods.current, windowMetric.current],
|
|
@@ -60635,13 +60796,15 @@ async function ga4Routes(app, opts) {
|
|
|
60635
60796
|
}
|
|
60636
60797
|
}
|
|
60637
60798
|
if (syncAi) {
|
|
60638
|
-
|
|
60639
|
-
|
|
60640
|
-
|
|
60641
|
-
|
|
60642
|
-
|
|
60643
|
-
|
|
60644
|
-
|
|
60799
|
+
for (const { range } of aiReports) {
|
|
60800
|
+
tx.delete(gaAiReferrals).where(
|
|
60801
|
+
and74(
|
|
60802
|
+
eq86(gaAiReferrals.projectId, project.id),
|
|
60803
|
+
sql37`${gaAiReferrals.date} >= ${range.startDate}`,
|
|
60804
|
+
sql37`${gaAiReferrals.date} <= ${range.endDate}`
|
|
60805
|
+
)
|
|
60806
|
+
).run();
|
|
60807
|
+
}
|
|
60645
60808
|
for (const row of aiReferrals) {
|
|
60646
60809
|
tx.insert(gaAiReferrals).values({
|
|
60647
60810
|
id: crypto51.randomUUID(),
|
|
@@ -60667,13 +60830,15 @@ async function ga4Routes(app, opts) {
|
|
|
60667
60830
|
}
|
|
60668
60831
|
}
|
|
60669
60832
|
if (syncSocial) {
|
|
60670
|
-
|
|
60671
|
-
|
|
60672
|
-
|
|
60673
|
-
|
|
60674
|
-
|
|
60675
|
-
|
|
60676
|
-
|
|
60833
|
+
for (const { range } of socialReports) {
|
|
60834
|
+
tx.delete(gaSocialReferrals).where(
|
|
60835
|
+
and74(
|
|
60836
|
+
eq86(gaSocialReferrals.projectId, project.id),
|
|
60837
|
+
sql37`${gaSocialReferrals.date} >= ${range.startDate}`,
|
|
60838
|
+
sql37`${gaSocialReferrals.date} <= ${range.endDate}`
|
|
60839
|
+
)
|
|
60840
|
+
).run();
|
|
60841
|
+
}
|
|
60677
60842
|
for (const row of socialReferrals) {
|
|
60678
60843
|
tx.insert(gaSocialReferrals).values({
|
|
60679
60844
|
id: crypto51.randomUUID(),
|
|
@@ -60690,7 +60855,7 @@ async function ga4Routes(app, opts) {
|
|
|
60690
60855
|
}
|
|
60691
60856
|
}
|
|
60692
60857
|
if (syncSummary) {
|
|
60693
|
-
tx.delete(gaTrafficSummaries).where(
|
|
60858
|
+
tx.delete(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).run();
|
|
60694
60859
|
tx.insert(gaTrafficSummaries).values({
|
|
60695
60860
|
id: crypto51.randomUUID(),
|
|
60696
60861
|
projectId: project.id,
|
|
@@ -60702,7 +60867,7 @@ async function ga4Routes(app, opts) {
|
|
|
60702
60867
|
syncedAt: now,
|
|
60703
60868
|
syncRunId: runId
|
|
60704
60869
|
}).run();
|
|
60705
|
-
tx.delete(gaTrafficWindowSummaries).where(
|
|
60870
|
+
tx.delete(gaTrafficWindowSummaries).where(eq86(gaTrafficWindowSummaries.projectId, project.id)).run();
|
|
60706
60871
|
for (const ws of windowSummaries) {
|
|
60707
60872
|
tx.insert(gaTrafficWindowSummaries).values({
|
|
60708
60873
|
id: crypto51.randomUUID(),
|
|
@@ -60719,9 +60884,16 @@ async function ga4Routes(app, opts) {
|
|
|
60719
60884
|
}).run();
|
|
60720
60885
|
}
|
|
60721
60886
|
}
|
|
60887
|
+
saveGaSyncCoverage(tx, trafficPlan, trafficReports.map((result) => result.range), now, runId);
|
|
60888
|
+
saveGaSyncCoverage(tx, dailyPlan, dailyReports.map((result) => result.report), now, runId);
|
|
60889
|
+
if (syncAi) saveGaSyncCoverage(tx, aiPlan, aiReports.map((result) => result.range), now, runId);
|
|
60890
|
+
if (syncSocial) saveGaSyncCoverage(tx, socialPlan, socialReports.map((result) => result.range), now, runId);
|
|
60722
60891
|
});
|
|
60723
60892
|
const [acquisitionSettled, leadsSettled, searchLandingSettled] = await measurementFetch;
|
|
60724
60893
|
const measurementNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
60894
|
+
if (resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain) !== propertyId) {
|
|
60895
|
+
throw validationError("GA4 property changed during sync; retry against the current connection");
|
|
60896
|
+
}
|
|
60725
60897
|
const recordMeasurementFailure = (component, reason) => {
|
|
60726
60898
|
componentFailure ??= { error: reason };
|
|
60727
60899
|
const message = describeError(reason);
|
|
@@ -60748,16 +60920,21 @@ async function ga4Routes(app, opts) {
|
|
|
60748
60920
|
return recordMeasurementFailure("acquisition", acquisitionSettled.reason);
|
|
60749
60921
|
}
|
|
60750
60922
|
try {
|
|
60751
|
-
|
|
60752
|
-
|
|
60753
|
-
|
|
60754
|
-
|
|
60755
|
-
|
|
60923
|
+
app.db.transaction((tx) => {
|
|
60924
|
+
for (const { report } of acquisitionSettled.value) {
|
|
60925
|
+
persistAcquisitionMeasurement(tx, {
|
|
60926
|
+
projectId: project.id,
|
|
60927
|
+
runId,
|
|
60928
|
+
syncedAt: measurementNow,
|
|
60929
|
+
report
|
|
60930
|
+
});
|
|
60931
|
+
}
|
|
60932
|
+
saveGaSyncCoverage(tx, acquisitionPlan, acquisitionSettled.value.map((result) => result.report), measurementNow, runId);
|
|
60756
60933
|
});
|
|
60757
60934
|
return {
|
|
60758
60935
|
status: "ready",
|
|
60759
60936
|
days: acquisitionDays,
|
|
60760
|
-
rowCount: acquisitionSettled.value.rows.length
|
|
60937
|
+
rowCount: acquisitionSettled.value.reduce((sum, result) => sum + result.report.rows.length, 0)
|
|
60761
60938
|
};
|
|
60762
60939
|
} catch (error) {
|
|
60763
60940
|
return recordMeasurementFailure("acquisition", error);
|
|
@@ -60772,18 +60949,23 @@ async function ga4Routes(app, opts) {
|
|
|
60772
60949
|
return { status: "not-configured", days: 0, rowCount: 0 };
|
|
60773
60950
|
}
|
|
60774
60951
|
try {
|
|
60775
|
-
|
|
60776
|
-
|
|
60777
|
-
|
|
60778
|
-
|
|
60779
|
-
|
|
60780
|
-
|
|
60952
|
+
app.db.transaction((tx) => {
|
|
60953
|
+
for (const { report } of leadsSettled.value) {
|
|
60954
|
+
persistLeadMeasurement(tx, {
|
|
60955
|
+
projectId: project.id,
|
|
60956
|
+
runId,
|
|
60957
|
+
syncedAt: measurementNow,
|
|
60958
|
+
report,
|
|
60959
|
+
eventNames: leadEventNames
|
|
60960
|
+
});
|
|
60961
|
+
}
|
|
60962
|
+
saveGaSyncCoverage(tx, leadPlan, leadsSettled.value.map((result) => result.report), measurementNow, runId);
|
|
60781
60963
|
});
|
|
60782
60964
|
return {
|
|
60783
60965
|
status: "ready",
|
|
60784
60966
|
days: leadDays,
|
|
60785
|
-
rowCount: leadsSettled.value.rows.length,
|
|
60786
|
-
attributionScope: leadsSettled.value.attributionScope,
|
|
60967
|
+
rowCount: leadsSettled.value.reduce((sum, result) => sum + result.report.rows.length, 0),
|
|
60968
|
+
attributionScope: leadsSettled.value.at(-1).report.attributionScope,
|
|
60787
60969
|
eventNames: leadEventNames,
|
|
60788
60970
|
keyEvents: keyEventRead
|
|
60789
60971
|
};
|
|
@@ -60826,19 +61008,24 @@ async function ga4Routes(app, opts) {
|
|
|
60826
61008
|
propertyId,
|
|
60827
61009
|
runId,
|
|
60828
61010
|
syncedAt: measurementNow,
|
|
60829
|
-
windows: report.windows
|
|
61011
|
+
windows: report.windows,
|
|
61012
|
+
reuse: {
|
|
61013
|
+
windows: GA_SEARCH_LANDING_WINDOWS.filter((window2) => !searchWindows.includes(window2)),
|
|
61014
|
+
timeZone: searchTimeZone,
|
|
61015
|
+
requestedAt: searchRequestedAt.toISOString()
|
|
61016
|
+
}
|
|
60830
61017
|
});
|
|
60831
61018
|
} catch (error) {
|
|
60832
61019
|
return recordSearchLandingFailure("error", error);
|
|
60833
61020
|
}
|
|
60834
61021
|
})();
|
|
60835
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: measurementNow }).where(
|
|
61022
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: measurementNow }).where(eq86(runs.id, runId)).run();
|
|
60836
61023
|
const syncedComponents = only ? [
|
|
60837
61024
|
...syncTraffic ? ["traffic"] : [],
|
|
60838
61025
|
...syncSummary ? ["summary"] : [],
|
|
60839
61026
|
...syncAi ? ["ai"] : [],
|
|
60840
61027
|
...syncSocial ? ["social"] : [],
|
|
60841
|
-
...searchLandingResult?.status === "ready" ? ["search-landing"] : []
|
|
61028
|
+
...searchLandingResult?.status === "ready" && searchLandingResult.windows.length > 0 ? ["search-landing"] : []
|
|
60842
61029
|
] : void 0;
|
|
60843
61030
|
gaLog("info", "sync.complete", {
|
|
60844
61031
|
projectId: project.id,
|
|
@@ -60878,7 +61065,7 @@ async function ga4Routes(app, opts) {
|
|
|
60878
61065
|
} catch (e) {
|
|
60879
61066
|
const msg = describeError(e);
|
|
60880
61067
|
gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
|
|
60881
|
-
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
61068
|
+
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq86(runs.id, runId)).run();
|
|
60882
61069
|
outcome2.report({ status: OutcomeStatuses.failed, ...ga4SyncFailure(e, credentialsResolved, opts, project) });
|
|
60883
61070
|
throw e;
|
|
60884
61071
|
}
|
|
@@ -60887,7 +61074,7 @@ async function ga4Routes(app, opts) {
|
|
|
60887
61074
|
const project = resolveProject(app.db, request.params.name);
|
|
60888
61075
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
60889
61076
|
const limit = Math.max(1, Math.min(parseInt(request.query.limit ?? "50", 10) || 50, 500));
|
|
60890
|
-
const range = resolveDateRange(request.query);
|
|
61077
|
+
const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
|
|
60891
61078
|
const windowSummaryRow = range.startDate && !range.explicitDates ? app.db.select({
|
|
60892
61079
|
periodStart: gaTrafficWindowSummaries.periodStart,
|
|
60893
61080
|
periodEnd: gaTrafficWindowSummaries.periodEnd,
|
|
@@ -60896,9 +61083,9 @@ async function ga4Routes(app, opts) {
|
|
|
60896
61083
|
totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
|
|
60897
61084
|
totalUsers: gaTrafficWindowSummaries.totalUsers
|
|
60898
61085
|
}).from(gaTrafficWindowSummaries).where(
|
|
60899
|
-
|
|
60900
|
-
|
|
60901
|
-
|
|
61086
|
+
and74(
|
|
61087
|
+
eq86(gaTrafficWindowSummaries.projectId, project.id),
|
|
61088
|
+
eq86(gaTrafficWindowSummaries.windowKey, range.window)
|
|
60902
61089
|
)
|
|
60903
61090
|
).get() : null;
|
|
60904
61091
|
const projectSummaryRow = app.db.select({
|
|
@@ -60907,11 +61094,11 @@ async function ga4Routes(app, opts) {
|
|
|
60907
61094
|
totalSessions: gaTrafficSummaries.totalSessions,
|
|
60908
61095
|
totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
|
|
60909
61096
|
totalUsers: gaTrafficSummaries.totalUsers
|
|
60910
|
-
}).from(gaTrafficSummaries).where(
|
|
61097
|
+
}).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).get();
|
|
60911
61098
|
const retainedBounds = [
|
|
60912
|
-
app.db.select({ min: sql37`MIN(${gaTrafficSnapshots.date})`, max: sql37`MAX(${gaTrafficSnapshots.date})` }).from(gaTrafficSnapshots).where(
|
|
60913
|
-
app.db.select({ min: sql37`MIN(${gaAiReferrals.date})`, max: sql37`MAX(${gaAiReferrals.date})` }).from(gaAiReferrals).where(
|
|
60914
|
-
app.db.select({ min: sql37`MIN(${gaSocialReferrals.date})`, max: sql37`MAX(${gaSocialReferrals.date})` }).from(gaSocialReferrals).where(
|
|
61099
|
+
app.db.select({ min: sql37`MIN(${gaTrafficSnapshots.date})`, max: sql37`MAX(${gaTrafficSnapshots.date})` }).from(gaTrafficSnapshots).where(eq86(gaTrafficSnapshots.projectId, project.id)).get(),
|
|
61100
|
+
app.db.select({ min: sql37`MIN(${gaAiReferrals.date})`, max: sql37`MAX(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq86(gaAiReferrals.projectId, project.id)).get(),
|
|
61101
|
+
app.db.select({ min: sql37`MIN(${gaSocialReferrals.date})`, max: sql37`MAX(${gaSocialReferrals.date})` }).from(gaSocialReferrals).where(eq86(gaSocialReferrals.projectId, project.id)).get()
|
|
60915
61102
|
];
|
|
60916
61103
|
const retainedDates = retainedBounds.flatMap((bound) => [bound?.min, bound?.max]).filter((date) => Boolean(date));
|
|
60917
61104
|
const projectSummaryCoversAll = Boolean(
|
|
@@ -60920,15 +61107,15 @@ async function ga4Routes(app, opts) {
|
|
|
60920
61107
|
const denominatorRow = windowSummaryRow ?? (range.window === "all" && projectSummaryCoversAll ? projectSummaryRow : null);
|
|
60921
61108
|
const denominatorPeriod = denominatorRow?.periodStart && denominatorRow.periodEnd ? { startDate: denominatorRow.periodStart, endDate: denominatorRow.periodEnd } : null;
|
|
60922
61109
|
const measuredRange = denominatorPeriod ? { ...range, ...denominatorPeriod } : range;
|
|
60923
|
-
const snapshotConditions = [
|
|
60924
|
-
const aiConditions = [
|
|
60925
|
-
const socialConditions = [
|
|
61110
|
+
const snapshotConditions = [eq86(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, measuredRange)];
|
|
61111
|
+
const aiConditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, measuredRange)];
|
|
61112
|
+
const socialConditions = [eq86(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, measuredRange)];
|
|
60926
61113
|
const snapshotTotalsRow = !denominatorRow ? (() => {
|
|
60927
61114
|
const summed = app.db.select({
|
|
60928
61115
|
totalSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
|
|
60929
61116
|
totalOrganicSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
|
|
60930
61117
|
totalUsers: sql37`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
|
|
60931
|
-
}).from(gaTrafficSnapshots).where(
|
|
61118
|
+
}).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).get();
|
|
60932
61119
|
return {
|
|
60933
61120
|
totalSessions: summed?.totalSessions ?? 0,
|
|
60934
61121
|
totalOrganicSessions: summed?.totalOrganicSessions ?? 0,
|
|
@@ -60941,21 +61128,21 @@ async function ga4Routes(app, opts) {
|
|
|
60941
61128
|
const summaryRow = denominatorRow ?? snapshotTotalsRow;
|
|
60942
61129
|
const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
|
|
60943
61130
|
totalDirectSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
|
|
60944
|
-
}).from(gaTrafficSnapshots).where(
|
|
61131
|
+
}).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).get();
|
|
60945
61132
|
const rows = app.db.select({
|
|
60946
61133
|
landingPage: sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
|
|
60947
61134
|
sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
|
|
60948
61135
|
organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
60949
61136
|
directSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
|
|
60950
61137
|
users: sql37`SUM(${gaTrafficSnapshots.users})`
|
|
60951
|
-
}).from(gaTrafficSnapshots).where(
|
|
61138
|
+
}).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
|
|
60952
61139
|
const aiReferralRows = app.db.select({
|
|
60953
61140
|
source: gaAiReferrals.source,
|
|
60954
61141
|
medium: gaAiReferrals.medium,
|
|
60955
61142
|
trafficClass: gaAiReferrals.trafficClass,
|
|
60956
61143
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
60957
61144
|
sessions: sql37`SUM(${gaAiReferrals.sessions})`
|
|
60958
|
-
}).from(gaAiReferrals).where(
|
|
61145
|
+
}).from(gaAiReferrals).where(and74(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.trafficClass, gaAiReferrals.sourceDimension).all();
|
|
60959
61146
|
const aiReferralLandingPageRows = app.db.select({
|
|
60960
61147
|
source: gaAiReferrals.source,
|
|
60961
61148
|
medium: gaAiReferrals.medium,
|
|
@@ -60963,7 +61150,7 @@ async function ga4Routes(app, opts) {
|
|
|
60963
61150
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
60964
61151
|
landingPage: sql37`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
60965
61152
|
sessions: sql37`SUM(${gaAiReferrals.sessions})`
|
|
60966
|
-
}).from(gaAiReferrals).where(
|
|
61153
|
+
}).from(gaAiReferrals).where(and74(...aiConditions)).groupBy(
|
|
60967
61154
|
gaAiReferrals.source,
|
|
60968
61155
|
gaAiReferrals.medium,
|
|
60969
61156
|
gaAiReferrals.trafficClass,
|
|
@@ -60978,7 +61165,7 @@ async function ga4Routes(app, opts) {
|
|
|
60978
61165
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
60979
61166
|
channelGroup: gaAiReferrals.channelGroup,
|
|
60980
61167
|
sessions: gaAiReferrals.sessions
|
|
60981
|
-
}).from(gaAiReferrals).where(
|
|
61168
|
+
}).from(gaAiReferrals).where(and74(...aiConditions)).all();
|
|
60982
61169
|
const aiReferrals = keepWinningDimensionRows(aiReferralRows, (r) => r.source);
|
|
60983
61170
|
const aiReferralLandingPages = keepWinningDimensionRows(
|
|
60984
61171
|
aiReferralLandingPageRows,
|
|
@@ -60995,16 +61182,16 @@ async function ga4Routes(app, opts) {
|
|
|
60995
61182
|
// dates counts a returning visitor once per day they returned. Same
|
|
60996
61183
|
// conclusion the AI-referral surfaces reached in 4.135.0.
|
|
60997
61184
|
sessions: sql37`SUM(${gaSocialReferrals.sessions})`
|
|
60998
|
-
}).from(gaSocialReferrals).where(
|
|
61185
|
+
}).from(gaSocialReferrals).where(and74(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql37`SUM(${gaSocialReferrals.sessions}) DESC`).all();
|
|
60999
61186
|
const socialDailyRows = app.db.select({
|
|
61000
61187
|
date: gaSocialReferrals.date,
|
|
61001
61188
|
source: gaSocialReferrals.source,
|
|
61002
61189
|
sessions: sql37`SUM(${gaSocialReferrals.sessions})`
|
|
61003
|
-
}).from(gaSocialReferrals).where(
|
|
61190
|
+
}).from(gaSocialReferrals).where(and74(...socialConditions)).groupBy(gaSocialReferrals.date, gaSocialReferrals.source).all();
|
|
61004
61191
|
const socialTotals = app.db.select({
|
|
61005
61192
|
sessions: sql37`SUM(${gaSocialReferrals.sessions})`
|
|
61006
|
-
}).from(gaSocialReferrals).where(
|
|
61007
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
61193
|
+
}).from(gaSocialReferrals).where(and74(...socialConditions)).get();
|
|
61194
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
61008
61195
|
const total = summaryRow?.totalSessions ?? 0;
|
|
61009
61196
|
const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
|
|
61010
61197
|
const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
|
|
@@ -61055,9 +61242,8 @@ async function ga4Routes(app, opts) {
|
|
|
61055
61242
|
// every date in the window) counted one visitor once per row. Withdrawn
|
|
61056
61243
|
// in 4.135.0; see `ga-ai-referral-aggregation.ts` for why no correct
|
|
61057
61244
|
// figure can be computed or fetched.
|
|
61058
|
-
// Shares of the rows' own sum,
|
|
61059
|
-
//
|
|
61060
|
-
// window, so it can exceed what the rows add up to.
|
|
61245
|
+
// Shares of the rows' own sum, which agrees with `aiSessionsDeduped`:
|
|
61246
|
+
// both count only the session lens across the same window.
|
|
61061
61247
|
aiReferrals: aiReferralDtos,
|
|
61062
61248
|
aiEngines: groupAiEngines(aiReferralDtos),
|
|
61063
61249
|
aiReferralLandingPages: aiReferralLandingPages.map((r) => ({
|
|
@@ -61126,8 +61312,8 @@ async function ga4Routes(app, opts) {
|
|
|
61126
61312
|
app.get("/projects/:name/ga/ai-referral-history", async (request, _reply) => {
|
|
61127
61313
|
const project = resolveProject(app.db, request.params.name);
|
|
61128
61314
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
61129
|
-
const range = resolveDateRange(request.query);
|
|
61130
|
-
const conditions = [
|
|
61315
|
+
const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
|
|
61316
|
+
const conditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
|
|
61131
61317
|
const rows = app.db.select({
|
|
61132
61318
|
date: gaAiReferrals.date,
|
|
61133
61319
|
source: gaAiReferrals.source,
|
|
@@ -61140,7 +61326,7 @@ async function ga4Routes(app, opts) {
|
|
|
61140
61326
|
// added up. ga4AiReferralDtoSchema.users was deprecated for this in
|
|
61141
61327
|
// 4.135.0; the history endpoint kept emitting one.
|
|
61142
61328
|
sessions: sql37`SUM(${gaAiReferrals.sessions})`
|
|
61143
|
-
}).from(gaAiReferrals).where(
|
|
61329
|
+
}).from(gaAiReferrals).where(and74(...conditions)).groupBy(
|
|
61144
61330
|
gaAiReferrals.date,
|
|
61145
61331
|
gaAiReferrals.source,
|
|
61146
61332
|
gaAiReferrals.medium,
|
|
@@ -61153,8 +61339,8 @@ async function ga4Routes(app, opts) {
|
|
|
61153
61339
|
app.get("/projects/:name/ga/ai-referral-daily", async (request, _reply) => {
|
|
61154
61340
|
const project = resolveProject(app.db, request.params.name);
|
|
61155
61341
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
61156
|
-
const range = resolveDateRange(request.query);
|
|
61157
|
-
const conditions = [
|
|
61342
|
+
const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
|
|
61343
|
+
const conditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
|
|
61158
61344
|
const rows = app.db.select({
|
|
61159
61345
|
date: gaAiReferrals.date,
|
|
61160
61346
|
source: gaAiReferrals.source,
|
|
@@ -61163,17 +61349,22 @@ async function ga4Routes(app, opts) {
|
|
|
61163
61349
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
61164
61350
|
channelGroup: gaAiReferrals.channelGroup,
|
|
61165
61351
|
sessions: gaAiReferrals.sessions
|
|
61166
|
-
}).from(gaAiReferrals).where(
|
|
61352
|
+
}).from(gaAiReferrals).where(and74(...conditions)).all();
|
|
61167
61353
|
const series = buildAiReferralDailySeries(rows);
|
|
61168
|
-
const measuredDates = app.db.selectDistinct({ date: gaDailyTotals.date }).from(gaDailyTotals).where(
|
|
61169
|
-
const firstAiDate = app.db.select({ date: sql37`MIN(${gaAiReferrals.date})` }).from(gaAiReferrals).where(
|
|
61170
|
-
|
|
61354
|
+
const measuredDates = app.db.selectDistinct({ date: gaDailyTotals.date }).from(gaDailyTotals).where(and74(eq86(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range))).all().map((row) => row.date);
|
|
61355
|
+
const firstAiDate = app.db.select({ date: sql37`MIN(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq86(gaAiReferrals.projectId, project.id)).get()?.date ?? null;
|
|
61356
|
+
const now = /* @__PURE__ */ new Date();
|
|
61357
|
+
return {
|
|
61358
|
+
...series,
|
|
61359
|
+
trend: aiReferralDailyTrend(series.days, measuredDates, firstAiDate, now),
|
|
61360
|
+
...aiReferralMonthlySeries({ days: series.days, byEngine: series.byEngine, measuredDates, range, firstAiDate, now })
|
|
61361
|
+
};
|
|
61171
61362
|
});
|
|
61172
61363
|
app.get("/projects/:name/ga/social-referral-history", async (request, _reply) => {
|
|
61173
61364
|
const project = resolveProject(app.db, request.params.name);
|
|
61174
61365
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
61175
|
-
const range = resolveDateRange(request.query);
|
|
61176
|
-
const conditions = [
|
|
61366
|
+
const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
|
|
61367
|
+
const conditions = [eq86(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, range)];
|
|
61177
61368
|
const rows = app.db.select({
|
|
61178
61369
|
date: gaSocialReferrals.date,
|
|
61179
61370
|
source: gaSocialReferrals.source,
|
|
@@ -61181,7 +61372,7 @@ async function ga4Routes(app, opts) {
|
|
|
61181
61372
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
61182
61373
|
sessions: gaSocialReferrals.sessions,
|
|
61183
61374
|
users: gaSocialReferrals.users
|
|
61184
|
-
}).from(gaSocialReferrals).where(
|
|
61375
|
+
}).from(gaSocialReferrals).where(and74(...conditions)).orderBy(gaSocialReferrals.date).all();
|
|
61185
61376
|
return rows;
|
|
61186
61377
|
});
|
|
61187
61378
|
app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
|
|
@@ -61194,8 +61385,8 @@ async function ga4Routes(app, opts) {
|
|
|
61194
61385
|
d.setDate(d.getDate() - n);
|
|
61195
61386
|
return fmt(d);
|
|
61196
61387
|
};
|
|
61197
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
61198
|
-
|
|
61388
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and74(
|
|
61389
|
+
eq86(gaSocialReferrals.projectId, project.id),
|
|
61199
61390
|
sql37`${gaSocialReferrals.date} >= ${from}`,
|
|
61200
61391
|
sql37`${gaSocialReferrals.date} < ${to}`
|
|
61201
61392
|
)).get();
|
|
@@ -61206,16 +61397,16 @@ async function ga4Routes(app, opts) {
|
|
|
61206
61397
|
const sourceCurrent = app.db.select({
|
|
61207
61398
|
source: gaSocialReferrals.source,
|
|
61208
61399
|
sessions: sql37`SUM(${gaSocialReferrals.sessions})`
|
|
61209
|
-
}).from(gaSocialReferrals).where(
|
|
61210
|
-
|
|
61400
|
+
}).from(gaSocialReferrals).where(and74(
|
|
61401
|
+
eq86(gaSocialReferrals.projectId, project.id),
|
|
61211
61402
|
sql37`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
|
|
61212
61403
|
sql37`${gaSocialReferrals.date} < ${fmt(today)}`
|
|
61213
61404
|
)).groupBy(gaSocialReferrals.source).all();
|
|
61214
61405
|
const sourcePrev = app.db.select({
|
|
61215
61406
|
source: gaSocialReferrals.source,
|
|
61216
61407
|
sessions: sql37`SUM(${gaSocialReferrals.sessions})`
|
|
61217
|
-
}).from(gaSocialReferrals).where(
|
|
61218
|
-
|
|
61408
|
+
}).from(gaSocialReferrals).where(and74(
|
|
61409
|
+
eq86(gaSocialReferrals.projectId, project.id),
|
|
61219
61410
|
sql37`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
|
|
61220
61411
|
sql37`${gaSocialReferrals.date} < ${daysAgo(7)}`
|
|
61221
61412
|
)).groupBy(gaSocialReferrals.source).all();
|
|
@@ -61240,16 +61431,16 @@ async function ga4Routes(app, opts) {
|
|
|
61240
61431
|
d.setDate(d.getDate() - n);
|
|
61241
61432
|
return fmt(d);
|
|
61242
61433
|
};
|
|
61243
|
-
const sumTotal = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
61244
|
-
const sumOrganic = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
61245
|
-
const sumDirect = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
61246
|
-
const sumAi = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
61247
|
-
|
|
61434
|
+
const sumTotal = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and74(eq86(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
61435
|
+
const sumOrganic = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and74(eq86(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
61436
|
+
const sumDirect = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and74(eq86(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
61437
|
+
const sumAi = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
|
|
61438
|
+
eq86(gaAiReferrals.projectId, project.id),
|
|
61248
61439
|
sql37`${gaAiReferrals.date} >= ${from}`,
|
|
61249
61440
|
sql37`${gaAiReferrals.date} < ${to}`,
|
|
61250
|
-
|
|
61441
|
+
eq86(gaAiReferrals.sourceDimension, "session")
|
|
61251
61442
|
)).get();
|
|
61252
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
61443
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and74(eq86(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${from}`, sql37`${gaSocialReferrals.date} < ${to}`)).get();
|
|
61253
61444
|
const todayStr = fmt(today);
|
|
61254
61445
|
const buildTrend = (sum) => {
|
|
61255
61446
|
const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
|
|
@@ -61258,20 +61449,20 @@ async function ga4Routes(app, opts) {
|
|
|
61258
61449
|
const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
|
|
61259
61450
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: deltaPercent(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: deltaPercent(c30, p30) };
|
|
61260
61451
|
};
|
|
61261
|
-
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
61262
|
-
|
|
61452
|
+
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
|
|
61453
|
+
eq86(gaAiReferrals.projectId, project.id),
|
|
61263
61454
|
sql37`${gaAiReferrals.date} >= ${daysAgo(7)}`,
|
|
61264
61455
|
sql37`${gaAiReferrals.date} < ${todayStr}`,
|
|
61265
|
-
|
|
61456
|
+
eq86(gaAiReferrals.sourceDimension, "session")
|
|
61266
61457
|
)).groupBy(gaAiReferrals.source).all();
|
|
61267
|
-
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
61268
|
-
|
|
61458
|
+
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
|
|
61459
|
+
eq86(gaAiReferrals.projectId, project.id),
|
|
61269
61460
|
sql37`${gaAiReferrals.date} >= ${daysAgo(14)}`,
|
|
61270
61461
|
sql37`${gaAiReferrals.date} < ${daysAgo(7)}`,
|
|
61271
|
-
|
|
61462
|
+
eq86(gaAiReferrals.sourceDimension, "session")
|
|
61272
61463
|
)).groupBy(gaAiReferrals.source).all();
|
|
61273
|
-
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(
|
|
61274
|
-
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(
|
|
61464
|
+
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and74(eq86(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql37`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
|
|
61465
|
+
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and74(eq86(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql37`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
|
|
61275
61466
|
const response = {
|
|
61276
61467
|
total: buildTrend(sumTotal),
|
|
61277
61468
|
organic: buildTrend(sumOrganic),
|
|
@@ -61287,14 +61478,14 @@ async function ga4Routes(app, opts) {
|
|
|
61287
61478
|
const project = resolveProject(app.db, request.params.name);
|
|
61288
61479
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
61289
61480
|
const window2 = parseGaOverviewWindow(request.query.window);
|
|
61290
|
-
const periodRows = app.db.select().from(gaWindowMetrics).where(
|
|
61481
|
+
const periodRows = app.db.select().from(gaWindowMetrics).where(and74(eq86(gaWindowMetrics.projectId, project.id), eq86(gaWindowMetrics.windowKey, window2))).all();
|
|
61291
61482
|
const current = periodRows.find((row) => row.period === GaOverviewPeriods.current) ?? null;
|
|
61292
61483
|
const previous = periodRows.find((row) => row.period === GaOverviewPeriods.previous) ?? null;
|
|
61293
61484
|
const fallback = gaOverviewComparisonRanges(window2);
|
|
61294
61485
|
const from = previous?.startDate ?? fallback.previous.startDate;
|
|
61295
61486
|
const to = current?.endDate ?? fallback.current.endDate;
|
|
61296
|
-
const daily = app.db.select().from(gaDailyTotals).where(
|
|
61297
|
-
|
|
61487
|
+
const daily = app.db.select().from(gaDailyTotals).where(and74(
|
|
61488
|
+
eq86(gaDailyTotals.projectId, project.id),
|
|
61298
61489
|
sql37`${gaDailyTotals.date} >= ${from}`,
|
|
61299
61490
|
sql37`${gaDailyTotals.date} <= ${to}`
|
|
61300
61491
|
)).all();
|
|
@@ -61303,16 +61494,16 @@ async function ga4Routes(app, opts) {
|
|
|
61303
61494
|
app.get("/projects/:name/ga/session-history", async (request, _reply) => {
|
|
61304
61495
|
const project = resolveProject(app.db, request.params.name);
|
|
61305
61496
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
61306
|
-
const range = resolveDateRange(request.query);
|
|
61307
|
-
const conditions = [
|
|
61497
|
+
const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
|
|
61498
|
+
const conditions = [eq86(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, range)];
|
|
61308
61499
|
const rows = app.db.select({
|
|
61309
61500
|
date: gaTrafficSnapshots.date,
|
|
61310
61501
|
sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
|
|
61311
61502
|
organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
61312
61503
|
users: sql37`SUM(${gaTrafficSnapshots.users})`
|
|
61313
|
-
}).from(gaTrafficSnapshots).where(
|
|
61314
|
-
const totalConditions = [
|
|
61315
|
-
const totals = app.db.select({ date: gaDailyTotals.date, users: gaDailyTotals.users }).from(gaDailyTotals).where(
|
|
61504
|
+
}).from(gaTrafficSnapshots).where(and74(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
61505
|
+
const totalConditions = [eq86(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range)];
|
|
61506
|
+
const totals = app.db.select({ date: gaDailyTotals.date, users: gaDailyTotals.users }).from(gaDailyTotals).where(and74(...totalConditions)).all();
|
|
61316
61507
|
return buildSessionHistory(
|
|
61317
61508
|
rows.map((r) => ({
|
|
61318
61509
|
date: r.date,
|
|
@@ -61331,7 +61522,7 @@ async function ga4Routes(app, opts) {
|
|
|
61331
61522
|
sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
|
|
61332
61523
|
organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
61333
61524
|
users: sql37`SUM(${gaTrafficSnapshots.users})`
|
|
61334
|
-
}).from(gaTrafficSnapshots).where(
|
|
61525
|
+
}).from(gaTrafficSnapshots).where(eq86(gaTrafficSnapshots.projectId, project.id)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
|
|
61335
61526
|
return {
|
|
61336
61527
|
pages: trafficPages.map((r) => ({
|
|
61337
61528
|
landingPage: r.landingPage,
|
|
@@ -61344,7 +61535,7 @@ async function ga4Routes(app, opts) {
|
|
|
61344
61535
|
}
|
|
61345
61536
|
|
|
61346
61537
|
// ../api-routes/src/ga-acquisition.ts
|
|
61347
|
-
import { and as
|
|
61538
|
+
import { and as and75, asc as asc22, count as count6, desc as desc39, eq as eq87, gte as gte24, lte as lte24, sql as sql38 } from "drizzle-orm";
|
|
61348
61539
|
async function gaAcquisitionRoutes(app) {
|
|
61349
61540
|
app.get("/projects/:name/ga/acquisition", async (request) => {
|
|
61350
61541
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -61367,14 +61558,14 @@ async function gaAcquisitionRoutes(app) {
|
|
|
61367
61558
|
hostName: gaAcquisitionDaily.hostName,
|
|
61368
61559
|
landingPage
|
|
61369
61560
|
};
|
|
61370
|
-
const windowFilters = [
|
|
61561
|
+
const windowFilters = [eq87(gaAcquisitionDaily.projectId, project.id)];
|
|
61371
61562
|
if (range.startDate) windowFilters.push(gte24(gaAcquisitionDaily.date, range.startDate));
|
|
61372
61563
|
if (range.endDate) windowFilters.push(lte24(gaAcquisitionDaily.date, range.endDate));
|
|
61373
61564
|
const filters = [...windowFilters];
|
|
61374
61565
|
if (query2.channel !== void 0) filters.push(sql38`lower(${gaAcquisitionDaily.channelGroup}) = lower(${query2.channel})`);
|
|
61375
61566
|
if (query2.hostName !== void 0) filters.push(sql38`lower(${gaAcquisitionDaily.hostName}) = lower(${query2.hostName})`);
|
|
61376
|
-
if (query2.source !== void 0) filters.push(
|
|
61377
|
-
if (query2.medium !== void 0) filters.push(
|
|
61567
|
+
if (query2.source !== void 0) filters.push(eq87(gaAcquisitionDaily.source, query2.source));
|
|
61568
|
+
if (query2.medium !== void 0) filters.push(eq87(gaAcquisitionDaily.medium, query2.medium));
|
|
61378
61569
|
const selectedLandingPage = query2.landingPage === void 0 ? void 0 : normalizeUrlPath(query2.landingPage) ?? query2.landingPage;
|
|
61379
61570
|
if (selectedLandingPage !== void 0) filters.push(sql38`${landingPage} = ${selectedLandingPage}`);
|
|
61380
61571
|
const pathPrefix = normalizePathPrefix(query2.pathPrefix);
|
|
@@ -61382,7 +61573,7 @@ async function gaAcquisitionRoutes(app) {
|
|
|
61382
61573
|
const pathname = sql38`case when instr(${landingPage}, '?') = 0 then ${landingPage} else substr(${landingPage}, 1, instr(${landingPage}, '?') - 1) end`;
|
|
61383
61574
|
filters.push(literalPathPrefixCondition(pathname, pathPrefix));
|
|
61384
61575
|
}
|
|
61385
|
-
const where =
|
|
61576
|
+
const where = and75(...filters);
|
|
61386
61577
|
const sessions = sql38`coalesce(sum(${gaAcquisitionDaily.sessions}), 0)`.mapWith(Number);
|
|
61387
61578
|
const selected = Object.fromEntries(groupBy.map((key) => [key, dimensions[key]]));
|
|
61388
61579
|
const grouped = app.db.select({ ...selected, sessions }).from(gaAcquisitionDaily).where(where).groupBy(...groupBy.map((key) => dimensions[key]));
|
|
@@ -61394,9 +61585,9 @@ async function gaAcquisitionRoutes(app) {
|
|
|
61394
61585
|
...groupBy.filter((key) => key !== GaAcquisitionDimensions.date).map((key) => asc22(dimensions[key]))
|
|
61395
61586
|
];
|
|
61396
61587
|
const rows = grouped.orderBy(...order).limit(limit).offset(offset).all();
|
|
61397
|
-
const coverage = app.db.select({ earliestDate: sql38`min(${gaAcquisitionDaily.date})`, latestDate: sql38`max(${gaAcquisitionDaily.date})` }).from(gaAcquisitionDaily).where(
|
|
61398
|
-
const channelGroups = app.db.selectDistinct({ channel: gaAcquisitionDaily.channelGroup }).from(gaAcquisitionDaily).where(
|
|
61399
|
-
const state = app.db.select().from(gaMeasurementSyncStates).where(
|
|
61588
|
+
const coverage = app.db.select({ earliestDate: sql38`min(${gaAcquisitionDaily.date})`, latestDate: sql38`max(${gaAcquisitionDaily.date})` }).from(gaAcquisitionDaily).where(eq87(gaAcquisitionDaily.projectId, project.id)).get();
|
|
61589
|
+
const channelGroups = app.db.selectDistinct({ channel: gaAcquisitionDaily.channelGroup }).from(gaAcquisitionDaily).where(and75(...windowFilters)).orderBy(asc22(gaAcquisitionDaily.channelGroup)).all().map((row) => row.channel);
|
|
61590
|
+
const state = app.db.select().from(gaMeasurementSyncStates).where(eq87(gaMeasurementSyncStates.projectId, project.id)).get();
|
|
61400
61591
|
const response = {
|
|
61401
61592
|
source: "ga4-acquisition",
|
|
61402
61593
|
status: state?.acquisitionStatus ?? "never-synced",
|
|
@@ -63128,7 +63319,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
63128
63319
|
|
|
63129
63320
|
// ../api-routes/src/backlinks.ts
|
|
63130
63321
|
import crypto53 from "crypto";
|
|
63131
|
-
import { and as
|
|
63322
|
+
import { and as and78, asc as asc23, desc as desc40, eq as eq89, sql as sql39 } from "drizzle-orm";
|
|
63132
63323
|
|
|
63133
63324
|
// ../integration-commoncrawl/src/constants.ts
|
|
63134
63325
|
import os2 from "os";
|
|
@@ -63578,7 +63769,7 @@ function pruneCachedRelease(release, opts = {}) {
|
|
|
63578
63769
|
}
|
|
63579
63770
|
|
|
63580
63771
|
// ../api-routes/src/backlinks-filter.ts
|
|
63581
|
-
import { and as
|
|
63772
|
+
import { and as and76, ne as ne15, notLike } from "drizzle-orm";
|
|
63582
63773
|
var BACKLINK_FILTER_PATTERNS = [
|
|
63583
63774
|
"*.google.com",
|
|
63584
63775
|
"*.googleusercontent.com",
|
|
@@ -63601,23 +63792,23 @@ function backlinkCrawlerExclusionClause() {
|
|
|
63601
63792
|
conditions.push(ne15(backlinkDomains.linkingDomain, pattern));
|
|
63602
63793
|
}
|
|
63603
63794
|
}
|
|
63604
|
-
const combined =
|
|
63795
|
+
const combined = and76(...conditions);
|
|
63605
63796
|
if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
|
|
63606
63797
|
return combined;
|
|
63607
63798
|
}
|
|
63608
63799
|
|
|
63609
63800
|
// ../api-routes/src/backlinks-source-state.ts
|
|
63610
|
-
import { and as
|
|
63801
|
+
import { and as and77, eq as eq88, isNotNull as isNotNull8, or as or17 } from "drizzle-orm";
|
|
63611
63802
|
function readCommonCrawlSourceState(db, projectId) {
|
|
63612
63803
|
const completedReleases = db.select({ status: ccReleaseSyncs.status }).from(ccReleaseSyncs).where(or17(
|
|
63613
|
-
|
|
63804
|
+
eq88(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready),
|
|
63614
63805
|
isNotNull8(ccReleaseSyncs.queryFinishedAt)
|
|
63615
63806
|
)).all();
|
|
63616
63807
|
const hasReadyRelease = completedReleases.length > 0;
|
|
63617
63808
|
const hasCurrentReadyRelease = completedReleases.some((release) => release.status === CcReleaseSyncStatuses.ready);
|
|
63618
|
-
const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(
|
|
63619
|
-
|
|
63620
|
-
|
|
63809
|
+
const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and77(
|
|
63810
|
+
eq88(backlinkSummaries.projectId, projectId),
|
|
63811
|
+
eq88(backlinkSummaries.source, BacklinkSources.commoncrawl)
|
|
63621
63812
|
)).limit(1).get() !== void 0;
|
|
63622
63813
|
return { hasReadyRelease, hasCurrentReadyRelease, hasProjectData, connected: hasReadyRelease && hasProjectData };
|
|
63623
63814
|
}
|
|
@@ -63688,11 +63879,11 @@ function mapRunRow(row) {
|
|
|
63688
63879
|
}
|
|
63689
63880
|
function latestSummaryForProject(db, projectId, source, release) {
|
|
63690
63881
|
const conditions = [
|
|
63691
|
-
|
|
63692
|
-
|
|
63882
|
+
eq89(backlinkSummaries.projectId, projectId),
|
|
63883
|
+
eq89(backlinkSummaries.source, source)
|
|
63693
63884
|
];
|
|
63694
|
-
if (release) conditions.push(
|
|
63695
|
-
const rows = db.select().from(backlinkSummaries).where(
|
|
63885
|
+
if (release) conditions.push(eq89(backlinkSummaries.release, release));
|
|
63886
|
+
const rows = db.select().from(backlinkSummaries).where(and78(...conditions)).orderBy(desc40(backlinkSummaries.queriedAt)).all();
|
|
63696
63887
|
return rows.reduce((latest, row) => {
|
|
63697
63888
|
if (!latest) return row;
|
|
63698
63889
|
const newer = isValidReleaseId(row.release) && isValidReleaseId(latest.release) && compareReleaseIds(row.release, latest.release) > 0;
|
|
@@ -63705,12 +63896,12 @@ function parseExcludeCrawlers(value) {
|
|
|
63705
63896
|
return lower === "1" || lower === "true" || lower === "yes";
|
|
63706
63897
|
}
|
|
63707
63898
|
function computeFilteredSummary(db, base) {
|
|
63708
|
-
const baseDomainCondition =
|
|
63709
|
-
|
|
63710
|
-
|
|
63711
|
-
|
|
63899
|
+
const baseDomainCondition = and78(
|
|
63900
|
+
eq89(backlinkDomains.projectId, base.projectId),
|
|
63901
|
+
eq89(backlinkDomains.source, base.source),
|
|
63902
|
+
eq89(backlinkDomains.release, base.release)
|
|
63712
63903
|
);
|
|
63713
|
-
const filteredCondition =
|
|
63904
|
+
const filteredCondition = and78(baseDomainCondition, backlinkCrawlerExclusionClause());
|
|
63714
63905
|
const unfilteredAgg = db.select({
|
|
63715
63906
|
count: sql39`count(*)`,
|
|
63716
63907
|
total: sql39`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
@@ -63751,7 +63942,7 @@ function computeFilteredReleaseTotals(db, projectId, source) {
|
|
|
63751
63942
|
SELECT ${backlinkDomains.release} AS release, ${backlinkDomains.numHosts} AS num_hosts, ${kept} AS kept,
|
|
63752
63943
|
ROW_NUMBER() OVER (PARTITION BY ${backlinkDomains.release}, ${kept} ORDER BY ${backlinkDomains.numHosts} DESC) AS kept_rank
|
|
63753
63944
|
FROM ${backlinkDomains}
|
|
63754
|
-
WHERE ${
|
|
63945
|
+
WHERE ${and78(eq89(backlinkDomains.projectId, projectId), eq89(backlinkDomains.source, source))}
|
|
63755
63946
|
)
|
|
63756
63947
|
GROUP BY release`);
|
|
63757
63948
|
return new Map(rows.map((row) => {
|
|
@@ -63765,13 +63956,13 @@ function computeFilteredReleaseTotals(db, projectId, source) {
|
|
|
63765
63956
|
}));
|
|
63766
63957
|
}
|
|
63767
63958
|
function buildSourceAvailability(db, projectId, source, connected2, excludeCrawlers) {
|
|
63768
|
-
const summary3 = db.select().from(backlinkSummaries).where(
|
|
63959
|
+
const summary3 = db.select().from(backlinkSummaries).where(and78(eq89(backlinkSummaries.projectId, projectId), eq89(backlinkSummaries.source, source))).orderBy(desc40(backlinkSummaries.queriedAt)).limit(1).get();
|
|
63769
63960
|
let totalLinkingDomains = summary3?.totalLinkingDomains ?? 0;
|
|
63770
63961
|
if (summary3 && excludeCrawlers) {
|
|
63771
|
-
const filtered = db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(
|
|
63772
|
-
|
|
63773
|
-
|
|
63774
|
-
|
|
63962
|
+
const filtered = db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(and78(
|
|
63963
|
+
eq89(backlinkDomains.projectId, projectId),
|
|
63964
|
+
eq89(backlinkDomains.source, source),
|
|
63965
|
+
eq89(backlinkDomains.release, summary3.release),
|
|
63775
63966
|
backlinkCrawlerExclusionClause()
|
|
63776
63967
|
)).get();
|
|
63777
63968
|
totalLinkingDomains = Number(filtered?.count ?? 0);
|
|
@@ -63841,7 +64032,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63841
64032
|
"@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
|
|
63842
64033
|
);
|
|
63843
64034
|
}
|
|
63844
|
-
const existing = app.db.select().from(ccReleaseSyncs).where(
|
|
64035
|
+
const existing = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.release, release)).get();
|
|
63845
64036
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
63846
64037
|
if (existing) {
|
|
63847
64038
|
if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
|
|
@@ -63852,9 +64043,9 @@ async function backlinksRoutes(app, opts) {
|
|
|
63852
64043
|
phaseDetail: null,
|
|
63853
64044
|
error: null,
|
|
63854
64045
|
updatedAt: now
|
|
63855
|
-
}).where(
|
|
64046
|
+
}).where(eq89(ccReleaseSyncs.id, existing.id)).run();
|
|
63856
64047
|
opts.onReleaseSyncRequested(existing.id, release);
|
|
63857
|
-
const refreshed = app.db.select().from(ccReleaseSyncs).where(
|
|
64048
|
+
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.id, existing.id)).get();
|
|
63858
64049
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
63859
64050
|
}
|
|
63860
64051
|
const id = crypto53.randomUUID();
|
|
@@ -63866,7 +64057,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63866
64057
|
updatedAt: now
|
|
63867
64058
|
}).run();
|
|
63868
64059
|
opts.onReleaseSyncRequested(id, release);
|
|
63869
|
-
const inserted = app.db.select().from(ccReleaseSyncs).where(
|
|
64060
|
+
const inserted = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.id, id)).get();
|
|
63870
64061
|
return reply.status(201).send(mapSyncRow(inserted));
|
|
63871
64062
|
});
|
|
63872
64063
|
app.get("/backlinks/syncs/latest", async (_request, reply) => {
|
|
@@ -63924,7 +64115,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63924
64115
|
createdAt: now
|
|
63925
64116
|
}).run();
|
|
63926
64117
|
opts.onBacklinkExtractRequested(runId, project.id, release);
|
|
63927
|
-
const run = app.db.select().from(runs).where(
|
|
64118
|
+
const run = app.db.select().from(runs).where(eq89(runs.id, runId)).get();
|
|
63928
64119
|
return reply.status(201).send(mapRunRow(run));
|
|
63929
64120
|
});
|
|
63930
64121
|
app.get(
|
|
@@ -63950,12 +64141,12 @@ async function backlinksRoutes(app, opts) {
|
|
|
63950
64141
|
const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
|
|
63951
64142
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
63952
64143
|
const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
|
|
63953
|
-
const baseDomainCondition =
|
|
63954
|
-
|
|
63955
|
-
|
|
63956
|
-
|
|
64144
|
+
const baseDomainCondition = and78(
|
|
64145
|
+
eq89(backlinkDomains.projectId, project.id),
|
|
64146
|
+
eq89(backlinkDomains.source, source),
|
|
64147
|
+
eq89(backlinkDomains.release, targetRelease)
|
|
63957
64148
|
);
|
|
63958
|
-
const domainCondition = excludeCrawlers ?
|
|
64149
|
+
const domainCondition = excludeCrawlers ? and78(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
|
|
63959
64150
|
const totalRow = app.db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
63960
64151
|
const rows = app.db.select({
|
|
63961
64152
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
@@ -63979,7 +64170,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63979
64170
|
async (request, reply) => {
|
|
63980
64171
|
const project = resolveProject(app.db, request.params.name);
|
|
63981
64172
|
const source = parseSourceParam(request.query.source);
|
|
63982
|
-
const rows = app.db.select().from(backlinkSummaries).where(
|
|
64173
|
+
const rows = app.db.select().from(backlinkSummaries).where(and78(eq89(backlinkSummaries.projectId, project.id), eq89(backlinkSummaries.source, source))).orderBy(asc23(backlinkSummaries.queriedAt)).all();
|
|
63983
64174
|
const response = rows.map((r) => ({
|
|
63984
64175
|
release: r.release,
|
|
63985
64176
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -64006,7 +64197,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
64006
64197
|
import crypto55 from "crypto";
|
|
64007
64198
|
import { isIP as isIP3 } from "net";
|
|
64008
64199
|
import { isDeepStrictEqual } from "util";
|
|
64009
|
-
import { and as
|
|
64200
|
+
import { and as and80, desc as desc41, eq as eq91, gt as gt7, gte as gte25, inArray as inArray41, lt as lt18, lte as lte26, sql as sql40 } from "drizzle-orm";
|
|
64010
64201
|
|
|
64011
64202
|
// ../api-routes/src/traffic-limits.ts
|
|
64012
64203
|
var VERCEL_MAX_SYNC_WINDOW_MS = 24 * 60 * 6e4;
|
|
@@ -65349,7 +65540,7 @@ async function ackCloudflareQueueMessages(client, options) {
|
|
|
65349
65540
|
}
|
|
65350
65541
|
|
|
65351
65542
|
// ../api-routes/src/traffic-sync-lease.ts
|
|
65352
|
-
import { and as
|
|
65543
|
+
import { and as and79, eq as eq90, isNull as isNull13, lte as lte25, or as or18 } from "drizzle-orm";
|
|
65353
65544
|
function leaseExpiresAt(now, ttlMs) {
|
|
65354
65545
|
const nowMs = Date.parse(now);
|
|
65355
65546
|
if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
|
|
@@ -65362,12 +65553,12 @@ function tryClaimTrafficSyncLease(input) {
|
|
|
65362
65553
|
const changed = tx.update(trafficSources).set({
|
|
65363
65554
|
syncLeaseOwner: input.owner,
|
|
65364
65555
|
syncLeaseExpiresAt: expiresAt
|
|
65365
|
-
}).where(
|
|
65366
|
-
|
|
65556
|
+
}).where(and79(
|
|
65557
|
+
eq90(trafficSources.id, input.sourceId),
|
|
65367
65558
|
or18(
|
|
65368
65559
|
isNull13(trafficSources.syncLeaseOwner),
|
|
65369
65560
|
lte25(trafficSources.syncLeaseExpiresAt, input.now),
|
|
65370
|
-
|
|
65561
|
+
eq90(trafficSources.syncLeaseOwner, input.owner)
|
|
65371
65562
|
)
|
|
65372
65563
|
)).run();
|
|
65373
65564
|
return changed.changes === 1;
|
|
@@ -65378,9 +65569,9 @@ function releaseTrafficSyncLease(input) {
|
|
|
65378
65569
|
const changed = tx.update(trafficSources).set({
|
|
65379
65570
|
syncLeaseOwner: null,
|
|
65380
65571
|
syncLeaseExpiresAt: null
|
|
65381
|
-
}).where(
|
|
65382
|
-
|
|
65383
|
-
|
|
65572
|
+
}).where(and79(
|
|
65573
|
+
eq90(trafficSources.id, input.sourceId),
|
|
65574
|
+
eq90(trafficSources.syncLeaseOwner, input.owner)
|
|
65384
65575
|
)).run();
|
|
65385
65576
|
return changed.changes === 1;
|
|
65386
65577
|
}, { behavior: "immediate" });
|
|
@@ -65447,14 +65638,14 @@ async function loadQueueReceiptPolicy(db, sourceId, readRetentionSeconds, warn2)
|
|
|
65447
65638
|
if (liveTtlMs === null) return { ttlMsAt: () => CLOUDFLARE_QUEUE_RECEIPT_TTL_MS };
|
|
65448
65639
|
const ttlMs = liveTtlMs;
|
|
65449
65640
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
65450
|
-
const alive =
|
|
65641
|
+
const alive = and80(eq91(trafficEventReceipts.sourceId, sourceId), gt7(trafficEventReceipts.expiresAt, now));
|
|
65451
65642
|
const lifetimeMs = sql40`(julianday(${trafficEventReceipts.expiresAt}) - julianday(${trafficEventReceipts.receivedAt})) * ${DAY_MS5}`;
|
|
65452
65643
|
const shortest = db.select({ ms: sql40`min(${lifetimeMs})` }).from(trafficEventReceipts).where(alive).get()?.ms ?? null;
|
|
65453
65644
|
if (shortest !== null && shortest < ttlMs - 1e3) {
|
|
65454
65645
|
const extended = sql40`strftime('%Y-%m-%dT%H:%M:%fZ', julianday(${trafficEventReceipts.receivedAt}) + ${ttlMs / DAY_MS5})`;
|
|
65455
|
-
db.update(trafficEventReceipts).set({ expiresAt: extended }).where(
|
|
65646
|
+
db.update(trafficEventReceipts).set({ expiresAt: extended }).where(and80(alive, lt18(trafficEventReceipts.expiresAt, extended))).run();
|
|
65456
65647
|
}
|
|
65457
|
-
const floorExpiry = db.select({ at: sql40`max(${trafficEventReceipts.expiresAt})` }).from(trafficEventReceipts).where(
|
|
65648
|
+
const floorExpiry = db.select({ at: sql40`max(${trafficEventReceipts.expiresAt})` }).from(trafficEventReceipts).where(and80(alive, sql40`${lifetimeMs} < ${CLOUDFLARE_QUEUE_RECEIPT_TTL_MS - 1e3}`)).get()?.at ?? null;
|
|
65458
65649
|
const floorMs = floorExpiry ? Date.parse(floorExpiry) : 0;
|
|
65459
65650
|
return { ttlMsAt: (receivedAt) => Math.max(ttlMs, floorMs - Date.parse(receivedAt)) };
|
|
65460
65651
|
}
|
|
@@ -65509,7 +65700,7 @@ function cloudflareIngestRateLimitKey(request, store, db) {
|
|
|
65509
65700
|
const authenticated = authenticateCloudflareIngest(request, store);
|
|
65510
65701
|
const params = request.params;
|
|
65511
65702
|
if (authenticated && typeof params.name === "string" && authenticated.credential.projectName === params.name) {
|
|
65512
|
-
const source = db.select().from(trafficSources).where(
|
|
65703
|
+
const source = db.select().from(trafficSources).where(eq91(trafficSources.id, authenticated.credential.sourceId)).get();
|
|
65513
65704
|
if (source && source.sourceType === TrafficSourceTypes.cloudflare && source.status === TrafficSourceStatuses.connected && parseDirectPushCloudflareSourceConfig(source.configJson) && timingSafeEqualHex(authenticated.bearerHash, source.ingestTokenHash)) {
|
|
65514
65705
|
return `cloudflare-source:${authenticated.credential.sourceId}`;
|
|
65515
65706
|
}
|
|
@@ -65581,22 +65772,39 @@ function assertCloudflareIngestUrlOutsideWorkerRoute(ingestUrl, workerRouteHost)
|
|
|
65581
65772
|
function emptyTrafficSeriesPoint(bucket) {
|
|
65582
65773
|
return { bucket, crawlerHits: 0, aiUserFetchHits: 0, aiReferralHits: 0, aiReferralLandedHits: 0, crawlerContentHits: 0, measured: true };
|
|
65583
65774
|
}
|
|
65584
|
-
|
|
65585
|
-
|
|
65586
|
-
|
|
65587
|
-
|
|
65588
|
-
|
|
65589
|
-
|
|
65590
|
-
|
|
65591
|
-
|
|
65592
|
-
|
|
65775
|
+
var SERIES_BUCKET_LENGTH = {
|
|
65776
|
+
[TrafficSeriesGranularities.hour]: null,
|
|
65777
|
+
[TrafficSeriesGranularities.day]: 10,
|
|
65778
|
+
[TrafficSeriesGranularities.month]: 7
|
|
65779
|
+
};
|
|
65780
|
+
function seriesBucketSql(tsHour, granularity) {
|
|
65781
|
+
const length = SERIES_BUCKET_LENGTH[granularity];
|
|
65782
|
+
return length === null ? tsHour : sql40`substr(${tsHour}, 1, ${sql40.raw(String(length))})`;
|
|
65783
|
+
}
|
|
65784
|
+
function seriesBucketOf(iso2, granularity) {
|
|
65785
|
+
const length = SERIES_BUCKET_LENGTH[granularity];
|
|
65786
|
+
return length === null ? iso2.slice(0, 13) : iso2.slice(0, length);
|
|
65787
|
+
}
|
|
65788
|
+
function seriesBucketStartMs(bucket, granularity) {
|
|
65789
|
+
if (granularity === TrafficSeriesGranularities.month) return Date.parse(`${bucket}-01T00:00:00.000Z`);
|
|
65790
|
+
if (granularity === TrafficSeriesGranularities.day) return Date.parse(`${bucket}T00:00:00.000Z`);
|
|
65791
|
+
return Date.parse(bucket.length === 13 ? `${bucket}:00:00.000Z` : bucket);
|
|
65792
|
+
}
|
|
65793
|
+
function nextSeriesBucketStartMs(startMs, granularity) {
|
|
65794
|
+
if (granularity === TrafficSeriesGranularities.month) {
|
|
65795
|
+
const start = new Date(startMs);
|
|
65796
|
+
return Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, 1);
|
|
65593
65797
|
}
|
|
65798
|
+
return startMs + (granularity === TrafficSeriesGranularities.day ? 864e5 : 36e5);
|
|
65799
|
+
}
|
|
65800
|
+
function completeTrafficSeries(windowStart, windowEnd, granularity, pointsByBucket) {
|
|
65801
|
+
const firstBucket = seriesBucketOf(windowStart.toISOString(), granularity);
|
|
65802
|
+
const lastBucketStartMs = seriesBucketStartMs(seriesBucketOf(windowEnd.toISOString(), granularity), granularity);
|
|
65594
65803
|
const points = [];
|
|
65595
|
-
|
|
65596
|
-
const
|
|
65804
|
+
for (let ms = seriesBucketStartMs(firstBucket, granularity); ms <= lastBucketStartMs; ms = nextSeriesBucketStartMs(ms, granularity)) {
|
|
65805
|
+
const iso2 = new Date(ms).toISOString();
|
|
65806
|
+
const bucket = granularity === TrafficSeriesGranularities.hour ? iso2 : seriesBucketOf(iso2, granularity);
|
|
65597
65807
|
points.push(pointsByBucket.get(bucket) ?? emptyTrafficSeriesPoint(bucket));
|
|
65598
|
-
if (granularity === TrafficSeriesGranularities.day) cursor.setUTCDate(cursor.getUTCDate() + 1);
|
|
65599
|
-
else cursor.setUTCHours(cursor.getUTCHours() + 1);
|
|
65600
65808
|
}
|
|
65601
65809
|
return points;
|
|
65602
65810
|
}
|
|
@@ -65752,7 +65960,7 @@ async function defaultResolveAccessToken(record2) {
|
|
|
65752
65960
|
);
|
|
65753
65961
|
}
|
|
65754
65962
|
function hasConnectedTrafficSourceSibling(db, projectId, sourceId) {
|
|
65755
|
-
return db.select().from(trafficSources).where(
|
|
65963
|
+
return db.select().from(trafficSources).where(eq91(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
|
|
65756
65964
|
}
|
|
65757
65965
|
function isAuthoritativeTrafficSource(db, source) {
|
|
65758
65966
|
return (source.status === TrafficSourceStatuses.connected || source.status === TrafficSourceStatuses.error) && !hasConnectedTrafficSourceSibling(db, source.projectId, source.id);
|
|
@@ -65779,13 +65987,13 @@ function wordpressBaseUrlIdentity(value) {
|
|
|
65779
65987
|
}
|
|
65780
65988
|
}
|
|
65781
65989
|
function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing = true) {
|
|
65782
|
-
const schedule = tx.select().from(schedules).where(
|
|
65783
|
-
|
|
65784
|
-
|
|
65990
|
+
const schedule = tx.select().from(schedules).where(and80(
|
|
65991
|
+
eq91(schedules.projectId, projectId),
|
|
65992
|
+
eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
65785
65993
|
)).get();
|
|
65786
65994
|
if (schedule) {
|
|
65787
65995
|
if (schedule.sourceId === sourceId) return { changed: false, created: false };
|
|
65788
|
-
tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(
|
|
65996
|
+
tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq91(schedules.id, schedule.id)).run();
|
|
65789
65997
|
return { changed: true, created: false };
|
|
65790
65998
|
}
|
|
65791
65999
|
if (!createIfMissing) return { changed: false, created: false };
|
|
@@ -65827,7 +66035,7 @@ async function runBackfillTask(options) {
|
|
|
65827
66035
|
};
|
|
65828
66036
|
const renewLease2 = setInterval(() => {
|
|
65829
66037
|
try {
|
|
65830
|
-
const latest = app.db.select().from(trafficSources).where(
|
|
66038
|
+
const latest = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
65831
66039
|
if (!latest || !isAuthoritativeTrafficSource(app.db, latest) || !isSameTrafficSourceGeneration(latest, sourceRow2)) {
|
|
65832
66040
|
clearInterval(renewLease2);
|
|
65833
66041
|
stopped.abort();
|
|
@@ -65875,10 +66083,10 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65875
66083
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
65876
66084
|
try {
|
|
65877
66085
|
app.db.transaction((tx) => {
|
|
65878
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
65879
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
66086
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq91(runs.id, runId)).run();
|
|
66087
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
65880
66088
|
if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
|
|
65881
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
66089
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
65882
66090
|
}
|
|
65883
66091
|
});
|
|
65884
66092
|
} catch {
|
|
@@ -65910,7 +66118,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65910
66118
|
let emptyOutcome;
|
|
65911
66119
|
try {
|
|
65912
66120
|
const completed = app.db.transaction((tx) => {
|
|
65913
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
66121
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
65914
66122
|
const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner);
|
|
65915
66123
|
if (stillAuthoritative) {
|
|
65916
66124
|
enforceRawEventSampleRetention(tx, sourceRow2.id, finishedAt2);
|
|
@@ -65919,7 +66127,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65919
66127
|
status: RunStatuses.failed,
|
|
65920
66128
|
error: "Traffic source was deactivated or reconfigured during backfill",
|
|
65921
66129
|
finishedAt: finishedAt2
|
|
65922
|
-
}).where(
|
|
66130
|
+
}).where(eq91(runs.id, runId)).run();
|
|
65923
66131
|
return Boolean(stillAuthoritative);
|
|
65924
66132
|
});
|
|
65925
66133
|
emptyOutcome = completed ? { status: OutcomeStatuses.skipped, reasonCode: OutcomeReasonCodes.NO_DATA } : { status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_CONNECTED };
|
|
@@ -65951,13 +66159,13 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65951
66159
|
const nextLastSyncedAt = Math.max(currentLastSyncedMs, windowEnd.getTime()) === windowEnd.getTime() ? windowEndIso : sourceRow2.lastSyncedAt;
|
|
65952
66160
|
try {
|
|
65953
66161
|
const commitOutcome = app.db.transaction((tx) => {
|
|
65954
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
66162
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
65955
66163
|
if (latestSource?.sourceType === TrafficSourceTypes.wordpress) {
|
|
65956
66164
|
tx.update(runs).set({
|
|
65957
66165
|
status: RunStatuses.failed,
|
|
65958
66166
|
error: "Generic WordPress replace backfill is unavailable because retained coverage is unproven.",
|
|
65959
66167
|
finishedAt
|
|
65960
|
-
}).where(
|
|
66168
|
+
}).where(eq91(runs.id, runId)).run();
|
|
65961
66169
|
return "wordpress-backfill-unsupported";
|
|
65962
66170
|
}
|
|
65963
66171
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
|
|
@@ -65965,7 +66173,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65965
66173
|
status: RunStatuses.failed,
|
|
65966
66174
|
error: "Traffic source was deactivated or reconfigured during backfill",
|
|
65967
66175
|
finishedAt
|
|
65968
|
-
}).where(
|
|
66176
|
+
}).where(eq91(runs.id, runId)).run();
|
|
65969
66177
|
return "source-inactive";
|
|
65970
66178
|
}
|
|
65971
66179
|
const rawSampleCutoff = enforceRawEventSampleRetention(
|
|
@@ -65974,37 +66182,37 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65974
66182
|
finishedAt
|
|
65975
66183
|
);
|
|
65976
66184
|
tx.delete(crawlerEventsHourly).where(
|
|
65977
|
-
|
|
65978
|
-
|
|
66185
|
+
and80(
|
|
66186
|
+
eq91(crawlerEventsHourly.sourceId, sourceRow2.id),
|
|
65979
66187
|
gte25(crawlerEventsHourly.tsHour, windowStartIso),
|
|
65980
66188
|
lte26(crawlerEventsHourly.tsHour, windowEndIso)
|
|
65981
66189
|
)
|
|
65982
66190
|
).run();
|
|
65983
66191
|
tx.delete(aiUserFetchEventsHourly).where(
|
|
65984
|
-
|
|
65985
|
-
|
|
66192
|
+
and80(
|
|
66193
|
+
eq91(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
|
|
65986
66194
|
gte25(aiUserFetchEventsHourly.tsHour, windowStartIso),
|
|
65987
66195
|
lte26(aiUserFetchEventsHourly.tsHour, windowEndIso)
|
|
65988
66196
|
)
|
|
65989
66197
|
).run();
|
|
65990
66198
|
clearLikelyFakeAgentHits(tx, { sourceId: sourceRow2.id, windowStart: windowStartIso, windowEnd: windowEndIso });
|
|
65991
66199
|
tx.delete(aiReferralEventsHourly).where(
|
|
65992
|
-
|
|
65993
|
-
|
|
66200
|
+
and80(
|
|
66201
|
+
eq91(aiReferralEventsHourly.sourceId, sourceRow2.id),
|
|
65994
66202
|
gte25(aiReferralEventsHourly.tsHour, windowStartIso),
|
|
65995
66203
|
lte26(aiReferralEventsHourly.tsHour, windowEndIso)
|
|
65996
66204
|
)
|
|
65997
66205
|
).run();
|
|
65998
66206
|
tx.delete(rawEventSamples).where(
|
|
65999
|
-
|
|
66000
|
-
|
|
66207
|
+
and80(
|
|
66208
|
+
eq91(rawEventSamples.sourceId, sourceRow2.id),
|
|
66001
66209
|
gte25(rawEventSamples.ts, windowStartIso),
|
|
66002
66210
|
lte26(rawEventSamples.ts, windowEndIso)
|
|
66003
66211
|
)
|
|
66004
66212
|
).run();
|
|
66005
66213
|
tx.delete(unrecognizedAgentEventsHourly).where(
|
|
66006
|
-
|
|
66007
|
-
|
|
66214
|
+
and80(
|
|
66215
|
+
eq91(unrecognizedAgentEventsHourly.sourceId, sourceRow2.id),
|
|
66008
66216
|
gte25(unrecognizedAgentEventsHourly.tsHour, windowStartIso),
|
|
66009
66217
|
lte26(unrecognizedAgentEventsHourly.tsHour, windowEndIso)
|
|
66010
66218
|
)
|
|
@@ -66212,9 +66420,9 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
66212
66420
|
FROM _migrations WHERE version = 207
|
|
66213
66421
|
`);
|
|
66214
66422
|
if (ruleChange?.appliedAt) {
|
|
66215
|
-
const olderReferral = tx.select({ sourceId: aiReferralEventsHourly.sourceId }).from(aiReferralEventsHourly).where(
|
|
66216
|
-
|
|
66217
|
-
|
|
66423
|
+
const olderReferral = tx.select({ sourceId: aiReferralEventsHourly.sourceId }).from(aiReferralEventsHourly).where(and80(
|
|
66424
|
+
eq91(aiReferralEventsHourly.sourceId, sourceRow2.id),
|
|
66425
|
+
lt18(aiReferralEventsHourly.createdAt, ruleChange.appliedAt)
|
|
66218
66426
|
)).limit(1).get();
|
|
66219
66427
|
if (!olderReferral) olderCountUntil = null;
|
|
66220
66428
|
}
|
|
@@ -66227,8 +66435,8 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
66227
66435
|
aiReferralOlderCountUntil: olderCountUntil,
|
|
66228
66436
|
...skipRecovered ? { skippedThroughAt: null } : {},
|
|
66229
66437
|
updatedAt: finishedAt
|
|
66230
|
-
}).where(
|
|
66231
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
66438
|
+
}).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
66439
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq91(runs.id, runId)).run();
|
|
66232
66440
|
return "committed";
|
|
66233
66441
|
});
|
|
66234
66442
|
reportBackfill(commitOutcome === "committed" ? {
|
|
@@ -66401,7 +66609,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66401
66609
|
};
|
|
66402
66610
|
const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
|
|
66403
66611
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
66404
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
66612
|
+
const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
|
|
66405
66613
|
const sourceId = activeSource?.id ?? crypto55.randomUUID();
|
|
66406
66614
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
66407
66615
|
if (activeSource) {
|
|
@@ -66411,7 +66619,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66411
66619
|
lastError: null,
|
|
66412
66620
|
configJson: config,
|
|
66413
66621
|
updatedAt: now
|
|
66414
|
-
}).where(
|
|
66622
|
+
}).where(eq91(trafficSources.id, sourceId)).run();
|
|
66415
66623
|
} else {
|
|
66416
66624
|
tx.insert(trafficSources).values({
|
|
66417
66625
|
id: sourceId,
|
|
@@ -66437,7 +66645,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66437
66645
|
entityId: sourceId
|
|
66438
66646
|
});
|
|
66439
66647
|
return {
|
|
66440
|
-
sourceRow: tx.select().from(trafficSources).where(
|
|
66648
|
+
sourceRow: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get(),
|
|
66441
66649
|
scheduleChanged: scheduleBinding.changed
|
|
66442
66650
|
};
|
|
66443
66651
|
}, { behavior: "immediate" });
|
|
@@ -66492,7 +66700,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66492
66700
|
const config = { baseUrl, username };
|
|
66493
66701
|
const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
|
|
66494
66702
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
66495
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
66703
|
+
const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
|
|
66496
66704
|
const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
|
|
66497
66705
|
const sourceId = endpointChanged ? crypto55.randomUUID() : activeSource?.id ?? crypto55.randomUUID();
|
|
66498
66706
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
@@ -66503,14 +66711,14 @@ async function trafficRoutes(app, opts) {
|
|
|
66503
66711
|
lastError: null,
|
|
66504
66712
|
configJson: config,
|
|
66505
66713
|
updatedAt: now
|
|
66506
|
-
}).where(
|
|
66714
|
+
}).where(eq91(trafficSources.id, sourceId)).run();
|
|
66507
66715
|
} else {
|
|
66508
66716
|
if (activeSource) {
|
|
66509
66717
|
tx.update(trafficSources).set({
|
|
66510
66718
|
status: TrafficSourceStatuses.archived,
|
|
66511
66719
|
archivedAt: now,
|
|
66512
66720
|
updatedAt: now
|
|
66513
|
-
}).where(
|
|
66721
|
+
}).where(eq91(trafficSources.id, activeSource.id)).run();
|
|
66514
66722
|
}
|
|
66515
66723
|
tx.insert(trafficSources).values({
|
|
66516
66724
|
id: sourceId,
|
|
@@ -66536,7 +66744,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66536
66744
|
entityId: sourceId
|
|
66537
66745
|
});
|
|
66538
66746
|
return {
|
|
66539
|
-
sourceRow: tx.select().from(trafficSources).where(
|
|
66747
|
+
sourceRow: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get(),
|
|
66540
66748
|
scheduleChanged: scheduleBinding.changed
|
|
66541
66749
|
};
|
|
66542
66750
|
}, { behavior: "immediate" });
|
|
@@ -66593,7 +66801,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66593
66801
|
const config = { projectId, teamId, environment };
|
|
66594
66802
|
const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
|
|
66595
66803
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
66596
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
66804
|
+
const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
|
|
66597
66805
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
66598
66806
|
let row;
|
|
66599
66807
|
if (activeSource) {
|
|
@@ -66603,8 +66811,8 @@ async function trafficRoutes(app, opts) {
|
|
|
66603
66811
|
lastError: null,
|
|
66604
66812
|
configJson: config,
|
|
66605
66813
|
updatedAt: now
|
|
66606
|
-
}).where(
|
|
66607
|
-
row = tx.select().from(trafficSources).where(
|
|
66814
|
+
}).where(eq91(trafficSources.id, activeSource.id)).run();
|
|
66815
|
+
row = tx.select().from(trafficSources).where(eq91(trafficSources.id, activeSource.id)).get();
|
|
66608
66816
|
} else {
|
|
66609
66817
|
const newId = crypto55.randomUUID();
|
|
66610
66818
|
tx.insert(trafficSources).values({
|
|
@@ -66629,7 +66837,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66629
66837
|
createdAt: now,
|
|
66630
66838
|
updatedAt: now
|
|
66631
66839
|
}).run();
|
|
66632
|
-
row = tx.select().from(trafficSources).where(
|
|
66840
|
+
row = tx.select().from(trafficSources).where(eq91(trafficSources.id, newId)).get();
|
|
66633
66841
|
}
|
|
66634
66842
|
const scheduleBinding = sourceStatus === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, row.id, now) : { changed: false, created: false };
|
|
66635
66843
|
writeAuditLog(tx, {
|
|
@@ -66673,7 +66881,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66673
66881
|
const { displayName: displayName2, zoneId: zoneId2, accountId: accountId2, queueId, queueName, retentionSeconds, apiToken } = parsed.data;
|
|
66674
66882
|
const workerRouteHost2 = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
|
|
66675
66883
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
66676
|
-
const cloudflareSources = app.db.select().from(trafficSources).where(
|
|
66884
|
+
const cloudflareSources = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
|
|
66677
66885
|
const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
|
|
66678
66886
|
if (sameModeSource) attempt.update({ action: "reauth" });
|
|
66679
66887
|
const sourceId2 = sameModeSource?.id ?? crypto55.randomUUID();
|
|
@@ -66727,7 +66935,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66727
66935
|
let scheduleChanged = false;
|
|
66728
66936
|
try {
|
|
66729
66937
|
const result = app.db.transaction((tx) => {
|
|
66730
|
-
const currentSameModeSource = tx.select().from(trafficSources).where(
|
|
66938
|
+
const currentSameModeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseQueuePullCloudflareSourceConfig(row.configJson) !== null);
|
|
66731
66939
|
if ((currentSameModeSource?.id ?? null) !== (sameModeSource?.id ?? null)) {
|
|
66732
66940
|
throw operationInProgress("Cloudflare Queue source changed during connect; retry");
|
|
66733
66941
|
}
|
|
@@ -66745,7 +66953,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66745
66953
|
queueBacklogObservedAt: null
|
|
66746
66954
|
} : {},
|
|
66747
66955
|
updatedAt: now2
|
|
66748
|
-
}).where(
|
|
66956
|
+
}).where(eq91(trafficSources.id, sourceId2)).run();
|
|
66749
66957
|
} else {
|
|
66750
66958
|
tx.insert(trafficSources).values({
|
|
66751
66959
|
id: sourceId2,
|
|
@@ -66765,7 +66973,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66765
66973
|
}
|
|
66766
66974
|
const changed = status === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, sourceId2, now2).changed : false;
|
|
66767
66975
|
return {
|
|
66768
|
-
row: tx.select().from(trafficSources).where(
|
|
66976
|
+
row: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId2)).get(),
|
|
66769
66977
|
scheduleChanged: changed
|
|
66770
66978
|
};
|
|
66771
66979
|
}, { behavior: "immediate" });
|
|
@@ -66815,7 +67023,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66815
67023
|
const { deliveryMode, displayName, zoneId, accountId } = parsed.data;
|
|
66816
67024
|
const workerRouteHost = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
|
|
66817
67025
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
66818
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
67026
|
+
const activeSource = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
|
|
66819
67027
|
if (activeSource) attempt.update({ action: "reauth" });
|
|
66820
67028
|
const sourceId = activeSource?.id ?? crypto55.randomUUID();
|
|
66821
67029
|
const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
|
|
@@ -66883,7 +67091,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66883
67091
|
let scheduleRemoved = false;
|
|
66884
67092
|
try {
|
|
66885
67093
|
const result = app.db.transaction((tx) => {
|
|
66886
|
-
const currentActiveSource = tx.select().from(trafficSources).where(
|
|
67094
|
+
const currentActiveSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.cloudflare && row2.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row2.configJson) !== null);
|
|
66887
67095
|
if ((currentActiveSource?.id ?? null) !== (activeSource?.id ?? null)) {
|
|
66888
67096
|
throw operationInProgress("Cloudflare direct-push source changed during connect; retry");
|
|
66889
67097
|
}
|
|
@@ -66897,8 +67105,8 @@ async function trafficRoutes(app, opts) {
|
|
|
66897
67105
|
configJson: config,
|
|
66898
67106
|
ingestTokenHash,
|
|
66899
67107
|
updatedAt: now
|
|
66900
|
-
}).where(
|
|
66901
|
-
row = tx.select().from(trafficSources).where(
|
|
67108
|
+
}).where(eq91(trafficSources.id, currentActiveSource.id)).run();
|
|
67109
|
+
row = tx.select().from(trafficSources).where(eq91(trafficSources.id, currentActiveSource.id)).get();
|
|
66902
67110
|
} else {
|
|
66903
67111
|
tx.insert(trafficSources).values({
|
|
66904
67112
|
id: sourceId,
|
|
@@ -66918,16 +67126,16 @@ async function trafficRoutes(app, opts) {
|
|
|
66918
67126
|
createdAt: now,
|
|
66919
67127
|
updatedAt: now
|
|
66920
67128
|
}).run();
|
|
66921
|
-
row = tx.select().from(trafficSources).where(
|
|
67129
|
+
row = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get();
|
|
66922
67130
|
}
|
|
66923
67131
|
let removed = false;
|
|
66924
67132
|
if (row.status === TrafficSourceStatuses.connected) {
|
|
66925
|
-
const schedule = tx.select().from(schedules).where(
|
|
66926
|
-
|
|
66927
|
-
|
|
67133
|
+
const schedule = tx.select().from(schedules).where(and80(
|
|
67134
|
+
eq91(schedules.projectId, project.id),
|
|
67135
|
+
eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
66928
67136
|
)).get();
|
|
66929
67137
|
if (schedule) {
|
|
66930
|
-
tx.delete(schedules).where(
|
|
67138
|
+
tx.delete(schedules).where(eq91(schedules.id, schedule.id)).run();
|
|
66931
67139
|
removed = true;
|
|
66932
67140
|
}
|
|
66933
67141
|
}
|
|
@@ -66983,47 +67191,47 @@ async function trafficRoutes(app, opts) {
|
|
|
66983
67191
|
};
|
|
66984
67192
|
}));
|
|
66985
67193
|
app.post("/projects/:name/traffic/sources/:id/activate", connectionRoute(app, (request) => {
|
|
66986
|
-
const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(
|
|
67194
|
+
const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
|
|
66987
67195
|
const integration = source ? TRAFFIC_SOURCE_INTEGRATIONS[source.sourceType] : void 0;
|
|
66988
67196
|
return integration ? { integration, action: "select" } : void 0;
|
|
66989
67197
|
}, async (request) => {
|
|
66990
67198
|
const project = resolveProject(app.db, request.params.name);
|
|
66991
67199
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
66992
67200
|
const result = app.db.transaction((tx) => {
|
|
66993
|
-
const target = tx.select().from(trafficSources).where(
|
|
67201
|
+
const target = tx.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
|
|
66994
67202
|
if (!target || target.projectId !== project.id || target.status === TrafficSourceStatuses.archived) {
|
|
66995
67203
|
throw notFound("Traffic source", request.params.id);
|
|
66996
67204
|
}
|
|
66997
67205
|
const { usesPullSchedule } = validateTrafficSourceCredential(target, project.name);
|
|
66998
|
-
const siblings = tx.select().from(trafficSources).where(
|
|
67206
|
+
const siblings = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all();
|
|
66999
67207
|
for (const sibling of siblings) {
|
|
67000
67208
|
if (sibling.id === target.id || sibling.status === TrafficSourceStatuses.archived) continue;
|
|
67001
67209
|
tx.update(trafficSources).set({
|
|
67002
67210
|
status: TrafficSourceStatuses.paused,
|
|
67003
67211
|
updatedAt: now
|
|
67004
|
-
}).where(
|
|
67212
|
+
}).where(eq91(trafficSources.id, sibling.id)).run();
|
|
67005
67213
|
}
|
|
67006
67214
|
tx.update(trafficSources).set({
|
|
67007
67215
|
status: TrafficSourceStatuses.connected,
|
|
67008
67216
|
lastError: null,
|
|
67009
67217
|
archivedAt: null,
|
|
67010
67218
|
updatedAt: now
|
|
67011
|
-
}).where(
|
|
67219
|
+
}).where(eq91(trafficSources.id, target.id)).run();
|
|
67012
67220
|
let scheduleAction = null;
|
|
67013
67221
|
if (usesPullSchedule) {
|
|
67014
67222
|
bindTrafficSyncSchedule(tx, project.id, target.id, now);
|
|
67015
67223
|
scheduleAction = "upsert";
|
|
67016
67224
|
} else {
|
|
67017
|
-
const schedule = tx.select().from(schedules).where(
|
|
67018
|
-
|
|
67019
|
-
|
|
67225
|
+
const schedule = tx.select().from(schedules).where(and80(
|
|
67226
|
+
eq91(schedules.projectId, project.id),
|
|
67227
|
+
eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
67020
67228
|
)).get();
|
|
67021
67229
|
if (schedule) {
|
|
67022
|
-
tx.delete(schedules).where(
|
|
67230
|
+
tx.delete(schedules).where(eq91(schedules.id, schedule.id)).run();
|
|
67023
67231
|
scheduleAction = "delete";
|
|
67024
67232
|
}
|
|
67025
67233
|
}
|
|
67026
|
-
const source = tx.select().from(trafficSources).where(
|
|
67234
|
+
const source = tx.select().from(trafficSources).where(eq91(trafficSources.id, target.id)).get();
|
|
67027
67235
|
writeAuditLog(tx, {
|
|
67028
67236
|
projectId: project.id,
|
|
67029
67237
|
actor: "api",
|
|
@@ -67091,7 +67299,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67091
67299
|
}
|
|
67092
67300
|
const { bearerHash, credential } = authenticated;
|
|
67093
67301
|
const sourceId = credential.sourceId;
|
|
67094
|
-
const sourceRow2 = app.db.select().from(trafficSources).where(
|
|
67302
|
+
const sourceRow2 = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get();
|
|
67095
67303
|
if (!sourceRow2 || sourceRow2.sourceType !== TrafficSourceTypes.cloudflare || sourceRow2.status !== TrafficSourceStatuses.connected || !parseDirectPushCloudflareSourceConfig(sourceRow2.configJson) || !timingSafeEqualHex(bearerHash, sourceRow2.ingestTokenHash)) {
|
|
67096
67304
|
throw authRequired();
|
|
67097
67305
|
}
|
|
@@ -67169,7 +67377,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67169
67377
|
});
|
|
67170
67378
|
app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
|
|
67171
67379
|
const project = resolveProject(app.db, request.params.name);
|
|
67172
|
-
const initialSourceRow = app.db.select().from(trafficSources).where(
|
|
67380
|
+
const initialSourceRow = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
|
|
67173
67381
|
if (!initialSourceRow || initialSourceRow.projectId !== project.id) {
|
|
67174
67382
|
throw notFound("Traffic source", request.params.id);
|
|
67175
67383
|
}
|
|
@@ -67242,7 +67450,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67242
67450
|
});
|
|
67243
67451
|
const finishSync = (status, error, stopReason) => {
|
|
67244
67452
|
const queueDrain = drainSummary(stopReason);
|
|
67245
|
-
app.db.update(runs).set({ status, finishedAt: committedAt, syncSummary: queueDrain, ...error ? { error } : {} }).where(
|
|
67453
|
+
app.db.update(runs).set({ status, finishedAt: committedAt, syncSummary: queueDrain, ...error ? { error } : {} }).where(eq91(runs.id, runId)).run();
|
|
67246
67454
|
writeAuditLog(app.db, {
|
|
67247
67455
|
projectId: project.id,
|
|
67248
67456
|
actor: "api",
|
|
@@ -67417,7 +67625,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67417
67625
|
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
67418
67626
|
ttlMs: CLOUDFLARE_QUEUE_SYNC_LEASE_TTL_MS
|
|
67419
67627
|
})) throw new Error("Queue sync lease was lost before acknowledgement");
|
|
67420
|
-
const latestSource = app.db.select().from(trafficSources).where(
|
|
67628
|
+
const latestSource = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
67421
67629
|
if (!latestSource || latestSource.projectId !== project.id || latestSource.status !== TrafficSourceStatuses.connected || latestSource.syncLeaseOwner !== leaseOwner || !isDeepStrictEqual(latestSource.configJson, sourceRow2.configJson)) {
|
|
67422
67630
|
throw validationError("Cloudflare Queue source was reconfigured before acknowledgement");
|
|
67423
67631
|
}
|
|
@@ -67506,13 +67714,13 @@ async function trafficRoutes(app, opts) {
|
|
|
67506
67714
|
error: safeError,
|
|
67507
67715
|
finishedAt: failedAt,
|
|
67508
67716
|
syncSummary: drainSummary(CloudflareQueueDrainStopReasons.error)
|
|
67509
|
-
}).where(
|
|
67510
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
67717
|
+
}).where(eq91(runs.id, runId)).run();
|
|
67718
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
67511
67719
|
if (latestSource && latestSource.status === TrafficSourceStatuses.connected && latestSource.syncLeaseOwner === leaseOwner && isDeepStrictEqual(latestSource.configJson, sourceRow2.configJson)) {
|
|
67512
|
-
tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(
|
|
67513
|
-
|
|
67514
|
-
|
|
67515
|
-
|
|
67720
|
+
tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and80(
|
|
67721
|
+
eq91(trafficSources.id, sourceRow2.id),
|
|
67722
|
+
eq91(trafficSources.status, TrafficSourceStatuses.connected),
|
|
67723
|
+
eq91(trafficSources.syncLeaseOwner, leaseOwner)
|
|
67516
67724
|
)).run();
|
|
67517
67725
|
}
|
|
67518
67726
|
});
|
|
@@ -67531,7 +67739,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67531
67739
|
`Sync for source type "${sourceRow2.sourceType}" is not implemented yet \u2014 only cloud-run, wordpress, and vercel are supported in v1.`
|
|
67532
67740
|
);
|
|
67533
67741
|
}
|
|
67534
|
-
const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(
|
|
67742
|
+
const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
|
|
67535
67743
|
if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
|
|
67536
67744
|
throw validationError("Traffic source must be active before it can sync");
|
|
67537
67745
|
}
|
|
@@ -67568,10 +67776,10 @@ async function trafficRoutes(app, opts) {
|
|
|
67568
67776
|
const markFailed = (msg, errorCode2) => {
|
|
67569
67777
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
67570
67778
|
app.db.transaction((tx) => {
|
|
67571
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
67572
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
67779
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq91(runs.id, runId)).run();
|
|
67780
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
67573
67781
|
if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && latestSource.syncLeaseOwner === syncLeaseOwner) {
|
|
67574
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
67782
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
67575
67783
|
}
|
|
67576
67784
|
});
|
|
67577
67785
|
try {
|
|
@@ -67665,7 +67873,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67665
67873
|
}
|
|
67666
67874
|
const credential = credentialStore.getConnection(project.name);
|
|
67667
67875
|
if (!credential) {
|
|
67668
|
-
app.db.delete(runs).where(
|
|
67876
|
+
app.db.delete(runs).where(eq91(runs.id, runId)).run();
|
|
67669
67877
|
throw validationError(
|
|
67670
67878
|
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
67671
67879
|
);
|
|
@@ -67699,14 +67907,14 @@ async function trafficRoutes(app, opts) {
|
|
|
67699
67907
|
const windowEndIso2 = windowEnd.toISOString();
|
|
67700
67908
|
const reservedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
67701
67909
|
const reservedSource = app.db.transaction((tx) => {
|
|
67702
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
67910
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
67703
67911
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== syncLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
|
|
67704
67912
|
tx.update(trafficSources).set({
|
|
67705
67913
|
lastSyncedAt: windowStartIso2,
|
|
67706
67914
|
wordpressPendingUntil: windowEndIso2,
|
|
67707
67915
|
updatedAt: reservedAt
|
|
67708
|
-
}).where(
|
|
67709
|
-
return tx.select().from(trafficSources).where(
|
|
67916
|
+
}).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
67917
|
+
return tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
67710
67918
|
}, { behavior: "immediate" });
|
|
67711
67919
|
if (!reservedSource) {
|
|
67712
67920
|
const message = "Traffic source changed while reserving the WordPress sync window; retry the sync.";
|
|
@@ -67766,12 +67974,12 @@ async function trafficRoutes(app, opts) {
|
|
|
67766
67974
|
auditAction = "traffic.vercel.synced";
|
|
67767
67975
|
const credentialStore = opts.vercelTrafficCredentialStore;
|
|
67768
67976
|
if (!credentialStore) {
|
|
67769
|
-
app.db.delete(runs).where(
|
|
67977
|
+
app.db.delete(runs).where(eq91(runs.id, runId)).run();
|
|
67770
67978
|
throw validationError("Vercel traffic credential storage is not configured for this deployment");
|
|
67771
67979
|
}
|
|
67772
67980
|
const credential = credentialStore.getConnection(project.name);
|
|
67773
67981
|
if (!credential) {
|
|
67774
|
-
app.db.delete(runs).where(
|
|
67982
|
+
app.db.delete(runs).where(eq91(runs.id, runId)).run();
|
|
67775
67983
|
throw validationError(
|
|
67776
67984
|
`No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
|
|
67777
67985
|
);
|
|
@@ -67789,7 +67997,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67789
67997
|
if (cappedStartMs > clampedStartMs) {
|
|
67790
67998
|
const previousSkip = sourceRow2.skippedThroughAt ? Date.parse(sourceRow2.skippedThroughAt) : Number.NaN;
|
|
67791
67999
|
const skippedThrough = Number.isFinite(previousSkip) ? new Date(Math.max(previousSkip, cappedStartMs)) : new Date(cappedStartMs);
|
|
67792
|
-
app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(
|
|
68000
|
+
app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
67793
68001
|
request.log.warn(
|
|
67794
68002
|
{
|
|
67795
68003
|
sourceId: sourceRow2.id,
|
|
@@ -67883,15 +68091,15 @@ async function trafficRoutes(app, opts) {
|
|
|
67883
68091
|
throw providerError(`WordPress sync failed: ${msg}`);
|
|
67884
68092
|
}
|
|
67885
68093
|
const commitOutcome = app.db.transaction((tx) => {
|
|
67886
|
-
const latestRow = tx.select().from(trafficSources).where(
|
|
67887
|
-
const latestHasConnectedSibling = tx.select().from(trafficSources).where(
|
|
68094
|
+
const latestRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
68095
|
+
const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
|
|
67888
68096
|
if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || latestRow.syncLeaseOwner !== syncLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
|
|
67889
68097
|
const abortedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
67890
68098
|
tx.update(runs).set({
|
|
67891
68099
|
status: RunStatuses.failed,
|
|
67892
68100
|
error: "Traffic source was deactivated or reconfigured during sync",
|
|
67893
68101
|
finishedAt: abortedAt
|
|
67894
|
-
}).where(
|
|
68102
|
+
}).where(eq91(runs.id, runId)).run();
|
|
67895
68103
|
return "source-inactive";
|
|
67896
68104
|
}
|
|
67897
68105
|
const previousIds = latestRow.lastEventIds ?? [];
|
|
@@ -68177,8 +68385,8 @@ async function trafficRoutes(app, opts) {
|
|
|
68177
68385
|
sourceUpdate.lastCursor = nextCursor ?? null;
|
|
68178
68386
|
sourceUpdate.wordpressPendingUntil = nextCursor ? windowEnd.toISOString() : null;
|
|
68179
68387
|
}
|
|
68180
|
-
tx.update(trafficSources).set(sourceUpdate).where(
|
|
68181
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
68388
|
+
tx.update(trafficSources).set(sourceUpdate).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
68389
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq91(runs.id, runId)).run();
|
|
68182
68390
|
return "committed";
|
|
68183
68391
|
}, { behavior: "immediate" });
|
|
68184
68392
|
if (commitOutcome === "source-inactive") {
|
|
@@ -68247,7 +68455,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68247
68455
|
});
|
|
68248
68456
|
app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => withFeatureOutcome(app, { feature: "server_traffic", operation: "backfill" }, async (recordOutcome) => {
|
|
68249
68457
|
const project = resolveProject(app.db, request.params.name);
|
|
68250
|
-
const sourceRow2 = app.db.select().from(trafficSources).where(
|
|
68458
|
+
const sourceRow2 = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
|
|
68251
68459
|
if (!sourceRow2 || sourceRow2.projectId !== project.id) {
|
|
68252
68460
|
throw notFound("Traffic source", request.params.id);
|
|
68253
68461
|
}
|
|
@@ -68372,7 +68580,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68372
68580
|
}
|
|
68373
68581
|
try {
|
|
68374
68582
|
app.db.transaction((tx) => {
|
|
68375
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
68583
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
68376
68584
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
|
|
68377
68585
|
throw validationError("Traffic source must remain active and unchanged before it can backfill");
|
|
68378
68586
|
}
|
|
@@ -68422,8 +68630,8 @@ async function trafficRoutes(app, opts) {
|
|
|
68422
68630
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
68423
68631
|
hits: sql40`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
68424
68632
|
}).from(crawlerEventsHourly).where(
|
|
68425
|
-
|
|
68426
|
-
|
|
68633
|
+
and80(
|
|
68634
|
+
eq91(crawlerEventsHourly.sourceId, row.id),
|
|
68427
68635
|
gte25(crawlerEventsHourly.tsHour, since)
|
|
68428
68636
|
)
|
|
68429
68637
|
).groupBy(crawlerEventsHourly.pathNormalized).all();
|
|
@@ -68432,14 +68640,14 @@ async function trafficRoutes(app, opts) {
|
|
|
68432
68640
|
);
|
|
68433
68641
|
const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
|
|
68434
68642
|
const aiUserFetchTotals = app.db.select({ total: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
68435
|
-
|
|
68436
|
-
|
|
68643
|
+
and80(
|
|
68644
|
+
eq91(aiUserFetchEventsHourly.sourceId, row.id),
|
|
68437
68645
|
gte25(aiUserFetchEventsHourly.tsHour, since)
|
|
68438
68646
|
)
|
|
68439
68647
|
).get();
|
|
68440
68648
|
const likelyFakeTotals = app.db.select({ total: sql40`COALESCE(SUM(${aiUserFetchLikelyFakeHourly.hits}), 0)` }).from(aiUserFetchLikelyFakeHourly).where(
|
|
68441
|
-
|
|
68442
|
-
|
|
68649
|
+
and80(
|
|
68650
|
+
eq91(aiUserFetchLikelyFakeHourly.sourceId, row.id),
|
|
68443
68651
|
gte25(aiUserFetchLikelyFakeHourly.tsHour, since)
|
|
68444
68652
|
)
|
|
68445
68653
|
).get();
|
|
@@ -68448,22 +68656,22 @@ async function trafficRoutes(app, opts) {
|
|
|
68448
68656
|
nonhop: sql40`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
|
|
68449
68657
|
landed: sql40`COALESCE(SUM(CASE WHEN ${countableReferralCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`
|
|
68450
68658
|
}).from(aiReferralEventsHourly).where(
|
|
68451
|
-
|
|
68452
|
-
|
|
68659
|
+
and80(
|
|
68660
|
+
eq91(aiReferralEventsHourly.sourceId, row.id),
|
|
68453
68661
|
gte25(aiReferralEventsHourly.tsHour, since)
|
|
68454
68662
|
)
|
|
68455
68663
|
).get();
|
|
68456
68664
|
const sampleTotals = app.db.select({ total: sql40`COUNT(*)` }).from(rawEventSamples).where(
|
|
68457
|
-
|
|
68458
|
-
|
|
68665
|
+
and80(
|
|
68666
|
+
eq91(rawEventSamples.sourceId, row.id),
|
|
68459
68667
|
gte25(rawEventSamples.ts, since)
|
|
68460
68668
|
)
|
|
68461
68669
|
).get();
|
|
68462
68670
|
const latestRun = app.db.select().from(runs).where(
|
|
68463
|
-
|
|
68464
|
-
|
|
68465
|
-
|
|
68466
|
-
|
|
68671
|
+
and80(
|
|
68672
|
+
eq91(runs.projectId, projectId),
|
|
68673
|
+
eq91(runs.kind, RunKinds["traffic-sync"]),
|
|
68674
|
+
eq91(runs.sourceId, row.id)
|
|
68467
68675
|
)
|
|
68468
68676
|
).orderBy(desc41(runs.startedAt)).limit(1).get();
|
|
68469
68677
|
return {
|
|
@@ -68501,7 +68709,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68501
68709
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
68502
68710
|
let updatedRow;
|
|
68503
68711
|
app.db.transaction((tx) => {
|
|
68504
|
-
const sourceRow2 = tx.select().from(trafficSources).where(
|
|
68712
|
+
const sourceRow2 = tx.select().from(trafficSources).where(and80(eq91(trafficSources.projectId, project.id), eq91(trafficSources.id, request.params.id))).get();
|
|
68505
68713
|
if (!sourceRow2) {
|
|
68506
68714
|
throw notFound("traffic source", request.params.id);
|
|
68507
68715
|
}
|
|
@@ -68515,7 +68723,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68515
68723
|
`Traffic source "${sourceRow2.id}" is staged. Activate it explicitly instead of resetting it.`
|
|
68516
68724
|
);
|
|
68517
68725
|
}
|
|
68518
|
-
const hasConnectedSibling = tx.select().from(trafficSources).where(
|
|
68726
|
+
const hasConnectedSibling = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
|
|
68519
68727
|
if (hasConnectedSibling) {
|
|
68520
68728
|
throw validationError(
|
|
68521
68729
|
`Traffic source "${sourceRow2.id}" is not authoritative. Activate it explicitly instead of resetting it.`
|
|
@@ -68532,7 +68740,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68532
68740
|
status: TrafficSourceStatuses.connected,
|
|
68533
68741
|
lastError: null,
|
|
68534
68742
|
updatedAt: now
|
|
68535
|
-
}).where(
|
|
68743
|
+
}).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
68536
68744
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
68537
68745
|
projectId: project.id,
|
|
68538
68746
|
actor: "api",
|
|
@@ -68540,7 +68748,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68540
68748
|
entityType: "traffic_source",
|
|
68541
68749
|
entityId: sourceRow2.id
|
|
68542
68750
|
}));
|
|
68543
|
-
updatedRow = tx.select().from(trafficSources).where(
|
|
68751
|
+
updatedRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
68544
68752
|
});
|
|
68545
68753
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
68546
68754
|
}));
|
|
@@ -68552,7 +68760,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68552
68760
|
}
|
|
68553
68761
|
let updatedRow;
|
|
68554
68762
|
app.db.transaction((tx) => {
|
|
68555
|
-
const sourceRow2 = tx.select().from(trafficSources).where(
|
|
68763
|
+
const sourceRow2 = tx.select().from(trafficSources).where(and80(eq91(trafficSources.projectId, project.id), eq91(trafficSources.id, request.params.id))).get();
|
|
68556
68764
|
if (!sourceRow2) {
|
|
68557
68765
|
throw notFound("traffic source", request.params.id);
|
|
68558
68766
|
}
|
|
@@ -68565,7 +68773,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68565
68773
|
);
|
|
68566
68774
|
}
|
|
68567
68775
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
68568
|
-
tx.update(trafficSources).set({ skippedThroughAt: null, updatedAt: now }).where(
|
|
68776
|
+
tx.update(trafficSources).set({ skippedThroughAt: null, updatedAt: now }).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
68569
68777
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
68570
68778
|
projectId: project.id,
|
|
68571
68779
|
actor: "api",
|
|
@@ -68574,20 +68782,20 @@ async function trafficRoutes(app, opts) {
|
|
|
68574
68782
|
entityId: sourceRow2.id,
|
|
68575
68783
|
diff: { skippedThroughAt: sourceRow2.skippedThroughAt, sourceType: sourceRow2.sourceType }
|
|
68576
68784
|
}));
|
|
68577
|
-
updatedRow = tx.select().from(trafficSources).where(
|
|
68785
|
+
updatedRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
68578
68786
|
});
|
|
68579
68787
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
68580
68788
|
});
|
|
68581
68789
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
68582
68790
|
const project = resolveProject(app.db, request.params.name);
|
|
68583
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
68791
|
+
const rows = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
|
|
68584
68792
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
|
|
68585
68793
|
const response = { sources };
|
|
68586
68794
|
return response;
|
|
68587
68795
|
});
|
|
68588
68796
|
app.get("/projects/:name/traffic/status", async (request) => {
|
|
68589
68797
|
const project = resolveProject(app.db, request.params.name);
|
|
68590
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
68798
|
+
const rows = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
|
|
68591
68799
|
const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
|
|
68592
68800
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
|
|
68593
68801
|
const response = { sources };
|
|
@@ -68597,7 +68805,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68597
68805
|
"/projects/:name/traffic/sources/:id",
|
|
68598
68806
|
async (request) => {
|
|
68599
68807
|
const project = resolveProject(app.db, request.params.name);
|
|
68600
|
-
const row = app.db.select().from(trafficSources).where(
|
|
68808
|
+
const row = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
|
|
68601
68809
|
if (!row || row.projectId !== project.id) {
|
|
68602
68810
|
throw notFound("Traffic source", request.params.id);
|
|
68603
68811
|
}
|
|
@@ -68642,11 +68850,12 @@ async function trafficRoutes(app, opts) {
|
|
|
68642
68850
|
const granularityParam = request.query?.granularity;
|
|
68643
68851
|
let granularity = TrafficSeriesGranularities.hour;
|
|
68644
68852
|
if (granularityParam !== void 0) {
|
|
68645
|
-
|
|
68646
|
-
|
|
68853
|
+
const parsed = trafficSeriesGranularitySchema.safeParse(granularityParam);
|
|
68854
|
+
if (parsed.success) {
|
|
68855
|
+
granularity = parsed.data;
|
|
68647
68856
|
} else {
|
|
68648
68857
|
throw validationError(
|
|
68649
|
-
`"granularity" must be one of: ${
|
|
68858
|
+
`"granularity" must be one of: ${trafficSeriesGranularitySchema.options.join(", ")}`
|
|
68650
68859
|
);
|
|
68651
68860
|
}
|
|
68652
68861
|
}
|
|
@@ -68665,12 +68874,12 @@ async function trafficRoutes(app, opts) {
|
|
|
68665
68874
|
const seriesByBucket = /* @__PURE__ */ new Map();
|
|
68666
68875
|
if (kinds.has(TrafficEventKinds.crawler)) {
|
|
68667
68876
|
const crawlerFilters = [
|
|
68668
|
-
|
|
68877
|
+
eq91(crawlerEventsHourly.projectId, project.id),
|
|
68669
68878
|
gte25(crawlerEventsHourly.tsHour, sinceIso),
|
|
68670
68879
|
lte26(crawlerEventsHourly.tsHour, untilIso)
|
|
68671
68880
|
];
|
|
68672
|
-
if (sourceIdParam) crawlerFilters.push(
|
|
68673
|
-
const crawlerWhere =
|
|
68881
|
+
if (sourceIdParam) crawlerFilters.push(eq91(crawlerEventsHourly.sourceId, sourceIdParam));
|
|
68882
|
+
const crawlerWhere = and80(...crawlerFilters);
|
|
68674
68883
|
const pathTotals = app.db.select({
|
|
68675
68884
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
68676
68885
|
hits: sql40`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
|
|
@@ -68681,7 +68890,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68681
68890
|
);
|
|
68682
68891
|
crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
|
|
68683
68892
|
totalEventRows += pathTotals.reduce((sum, row) => sum + Number(row.rows), 0);
|
|
68684
|
-
const crawlerSeriesBucket =
|
|
68893
|
+
const crawlerSeriesBucket = seriesBucketSql(crawlerEventsHourly.tsHour, granularity);
|
|
68685
68894
|
const distinctPaths = pathTotals.map((r) => ({ pathNormalized: r.pathNormalized }));
|
|
68686
68895
|
const contentPaths = [];
|
|
68687
68896
|
const infraPaths = [];
|
|
@@ -68740,19 +68949,19 @@ async function trafficRoutes(app, opts) {
|
|
|
68740
68949
|
}
|
|
68741
68950
|
if (kinds.has(TrafficEventKinds["ai-user-fetch"])) {
|
|
68742
68951
|
const userFetchFilters = [
|
|
68743
|
-
|
|
68952
|
+
eq91(aiUserFetchEventsHourly.projectId, project.id),
|
|
68744
68953
|
gte25(aiUserFetchEventsHourly.tsHour, sinceIso),
|
|
68745
68954
|
lte26(aiUserFetchEventsHourly.tsHour, untilIso)
|
|
68746
68955
|
];
|
|
68747
|
-
if (sourceIdParam) userFetchFilters.push(
|
|
68748
|
-
const userFetchWhere =
|
|
68956
|
+
if (sourceIdParam) userFetchFilters.push(eq91(aiUserFetchEventsHourly.sourceId, sourceIdParam));
|
|
68957
|
+
const userFetchWhere = and80(...userFetchFilters);
|
|
68749
68958
|
const total = app.db.select({
|
|
68750
68959
|
total: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
|
|
68751
68960
|
rows: sql40`COUNT(*)`
|
|
68752
68961
|
}).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
|
|
68753
68962
|
aiUserFetchTotal = Number(total?.total ?? 0);
|
|
68754
68963
|
totalEventRows += Number(total?.rows ?? 0);
|
|
68755
|
-
const userFetchSeriesBucket =
|
|
68964
|
+
const userFetchSeriesBucket = seriesBucketSql(aiUserFetchEventsHourly.tsHour, granularity);
|
|
68756
68965
|
const userFetchSeries = app.db.select({
|
|
68757
68966
|
bucket: userFetchSeriesBucket,
|
|
68758
68967
|
hits: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
|
|
@@ -68779,12 +68988,12 @@ async function trafficRoutes(app, opts) {
|
|
|
68779
68988
|
}
|
|
68780
68989
|
if (kinds.has(TrafficEventKinds["ai-referral"])) {
|
|
68781
68990
|
const aiFilters = [
|
|
68782
|
-
|
|
68991
|
+
eq91(aiReferralEventsHourly.projectId, project.id),
|
|
68783
68992
|
gte25(aiReferralEventsHourly.tsHour, sinceIso),
|
|
68784
68993
|
lte26(aiReferralEventsHourly.tsHour, untilIso)
|
|
68785
68994
|
];
|
|
68786
|
-
if (sourceIdParam) aiFilters.push(
|
|
68787
|
-
const aiWhere =
|
|
68995
|
+
if (sourceIdParam) aiFilters.push(eq91(aiReferralEventsHourly.sourceId, sourceIdParam));
|
|
68996
|
+
const aiWhere = and80(...aiFilters);
|
|
68788
68997
|
const total = app.db.select({
|
|
68789
68998
|
total: sql40`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
|
|
68790
68999
|
nonhop: sql40`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
|
|
@@ -68802,7 +69011,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68802
69011
|
Number(total?.organic ?? 0)
|
|
68803
69012
|
);
|
|
68804
69013
|
totalEventRows += Number(total?.rows ?? 0);
|
|
68805
|
-
const referralSeriesBucket =
|
|
69014
|
+
const referralSeriesBucket = seriesBucketSql(aiReferralEventsHourly.tsHour, granularity);
|
|
68806
69015
|
const referralSeries = app.db.select({
|
|
68807
69016
|
bucket: referralSeriesBucket,
|
|
68808
69017
|
hits: sql40`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
|
|
@@ -68859,7 +69068,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68859
69068
|
windowEnd: untilIso,
|
|
68860
69069
|
series: (() => {
|
|
68861
69070
|
const points = completeTrafficSeries(since, until, granularity, seriesByBucket);
|
|
68862
|
-
const coverageScope = (col, srcCol) => sourceIdParam ?
|
|
69071
|
+
const coverageScope = (col, srcCol) => sourceIdParam ? and80(eq91(col, project.id), eq91(srcCol, sourceIdParam)) : eq91(col, project.id);
|
|
68863
69072
|
const firsts = [
|
|
68864
69073
|
app.db.select({ v: sql40`MIN(${crawlerEventsHourly.tsHour})` }).from(crawlerEventsHourly).where(coverageScope(crawlerEventsHourly.projectId, crawlerEventsHourly.sourceId)).get()?.v,
|
|
68865
69074
|
app.db.select({ v: sql40`MIN(${aiUserFetchEventsHourly.tsHour})` }).from(aiUserFetchEventsHourly).where(coverageScope(aiUserFetchEventsHourly.projectId, aiUserFetchEventsHourly.sourceId)).get()?.v,
|
|
@@ -68868,32 +69077,34 @@ async function trafficRoutes(app, opts) {
|
|
|
68868
69077
|
const referralCountingChangedAt = aiReferralOlderCountUntil(app.db, { projectId: project.id, sourceId: sourceIdParam });
|
|
68869
69078
|
const referralCountingChangedMs = referralCountingChangedAt === null ? null : Date.parse(referralCountingChangedAt);
|
|
68870
69079
|
const coverageStart = firsts.length ? firsts.slice().sort()[0] : null;
|
|
68871
|
-
const bucketStartMs = (bucket) =>
|
|
68872
|
-
granularity === TrafficSeriesGranularities.day ? `${bucket}T00:00:00.000Z` : bucket
|
|
68873
|
-
);
|
|
68874
|
-
const bucketMs = granularity === TrafficSeriesGranularities.day ? 864e5 : 36e5;
|
|
69080
|
+
const bucketStartMs = (bucket) => seriesBucketStartMs(bucket, granularity);
|
|
68875
69081
|
const lastBucket = points.length ? points[points.length - 1].bucket : null;
|
|
68876
69082
|
const lastBucketStartMs = lastBucket === null ? null : bucketStartMs(lastBucket);
|
|
68877
|
-
const trailingBucketIsPartial = lastBucketStartMs !== null && Number.isFinite(lastBucketStartMs) && until.getTime() < lastBucketStartMs
|
|
68878
|
-
const coverageKey = coverageStart === null ? null :
|
|
69083
|
+
const trailingBucketIsPartial = lastBucketStartMs !== null && Number.isFinite(lastBucketStartMs) && until.getTime() < nextSeriesBucketStartMs(lastBucketStartMs, granularity);
|
|
69084
|
+
const coverageKey = coverageStart === null ? null : seriesBucketOf(coverageStart, granularity);
|
|
68879
69085
|
for (const pt of points) {
|
|
68880
|
-
pt.measured = coverageKey === null ? false : (
|
|
69086
|
+
pt.measured = coverageKey === null ? false : seriesBucketOf(pt.bucket, granularity) >= coverageKey;
|
|
68881
69087
|
}
|
|
68882
69088
|
const firstMeasuredIdx = points.findIndex((pt) => pt.measured);
|
|
68883
69089
|
const coverageStartMs = coverageStart === null ? null : Date.parse(coverageStart);
|
|
68884
|
-
const leadingBucketIsPartial = firstMeasuredIdx >= 0 && coverageStartMs !== null && Number.isFinite(coverageStartMs) && coverageStartMs > bucketStartMs(points[firstMeasuredIdx].bucket);
|
|
69090
|
+
const leadingBucketIsPartial = firstMeasuredIdx >= 0 && coverageStartMs !== null && Number.isFinite(coverageStartMs) && Math.max(coverageStartMs, since.getTime()) > bucketStartMs(points[firstMeasuredIdx].bucket);
|
|
68885
69091
|
return {
|
|
68886
69092
|
granularity,
|
|
68887
69093
|
points,
|
|
68888
69094
|
coverageStart,
|
|
68889
69095
|
aiReferralCountingChangedAt: referralCountingChangedAt,
|
|
69096
|
+
// The same two edges the fits leave out, named for a chart to draw as partial.
|
|
69097
|
+
partialBuckets: [
|
|
69098
|
+
...firstMeasuredIdx >= 0 && leadingBucketIsPartial ? [points[firstMeasuredIdx].bucket] : [],
|
|
69099
|
+
...trailingBucketIsPartial && lastBucket !== null && !(points.length - 1 === firstMeasuredIdx && leadingBucketIsPartial) ? [lastBucket] : []
|
|
69100
|
+
],
|
|
68890
69101
|
// Two edges are excluded from the fit, both for the same reason: a
|
|
68891
69102
|
// bucket that is not a full reading must not be treated as one.
|
|
68892
69103
|
//
|
|
68893
69104
|
// Leading: unmeasured buckets predate recording, so they are the
|
|
68894
69105
|
// ABSENCE of a reading, not a reading of zero. The first MEASURED
|
|
68895
|
-
// bucket goes too when recording began inside it, since
|
|
68896
|
-
// a bucket read as a whole
|
|
69106
|
+
// bucket goes too when recording or the range began inside it, since
|
|
69107
|
+
// a fraction of a bucket read as a whole slopes the fit up out of nothing.
|
|
68897
69108
|
//
|
|
68898
69109
|
// Trailing: `until` defaults to now, so the newest bucket holds only
|
|
68899
69110
|
// the elapsed fraction of the current period. A site flat at 500/day
|
|
@@ -68946,7 +69157,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68946
69157
|
}
|
|
68947
69158
|
|
|
68948
69159
|
// ../api-routes/src/doctor/checks/report-sweeps.ts
|
|
68949
|
-
import { and as
|
|
69160
|
+
import { and as and81, asc as asc24, eq as eq92, gte as gte26, lte as lte27 } from "drizzle-orm";
|
|
68950
69161
|
|
|
68951
69162
|
// ../api-routes/src/visibility-attribution.ts
|
|
68952
69163
|
function buildQueryAttribution2(projectQueries) {
|
|
@@ -68980,11 +69191,11 @@ function inspectSweep(ctx, run) {
|
|
|
68980
69191
|
answerMentioned: querySnapshots.answerMentioned,
|
|
68981
69192
|
answerText: querySnapshots.answerText,
|
|
68982
69193
|
measurementExecutionId: querySnapshots.measurementExecutionId
|
|
68983
|
-
}).from(querySnapshots).where(
|
|
69194
|
+
}).from(querySnapshots).where(eq92(querySnapshots.runId, run.id)).all();
|
|
68984
69195
|
if (snapshots.length === 0) return { detail: { ...base, complete: false, reason: "no-answers" } };
|
|
68985
69196
|
try {
|
|
68986
69197
|
if (run.measurementPlanVersionId) {
|
|
68987
|
-
const version = ctx.db.select().from(measurementPlanVersions).where(
|
|
69198
|
+
const version = ctx.db.select().from(measurementPlanVersions).where(and81(eq92(measurementPlanVersions.projectId, ctx.project.id), eq92(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
|
|
68988
69199
|
if (!version) return { detail: { ...base, complete: false, reason: "missing-plan" } };
|
|
68989
69200
|
const plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
|
|
68990
69201
|
const manifest = measurementRunExpectedSlots(run, plan);
|
|
@@ -69016,10 +69227,10 @@ function inspectSweep(ctx, run) {
|
|
|
69016
69227
|
};
|
|
69017
69228
|
return unbound ? { detail } : { detail, coverage: slotCoverage(expected2, answered3) };
|
|
69018
69229
|
}
|
|
69019
|
-
const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(
|
|
69230
|
+
const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq92(simpleMeasurementDefinitions.runId, run.id)).get();
|
|
69020
69231
|
const frozen = stored ? simpleMeasurementDefinitionSchema.parse(stored.definition) : null;
|
|
69021
|
-
const project = ctx.db.select().from(projects).where(
|
|
69022
|
-
const basket = frozen ? frozen.queries.map((query2) => ({ id: query2.queryId, query: query2.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(
|
|
69232
|
+
const project = ctx.db.select().from(projects).where(eq92(projects.id, ctx.project.id)).get();
|
|
69233
|
+
const basket = frozen ? frozen.queries.map((query2) => ({ id: query2.queryId, query: query2.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq92(queries.projectId, project.id)).all();
|
|
69023
69234
|
const providers = frozen ? frozen.engines.map((engine) => engine.provider) : project.providers.length > 0 ? project.providers : [...new Set(snapshots.map((snapshot) => snapshot.provider))];
|
|
69024
69235
|
const attribution = buildQueryAttribution2(basket);
|
|
69025
69236
|
const expected = new Set(basket.flatMap((query2) => providers.map((provider) => JSON.stringify([query2.id, provider.trim().toLowerCase()]))));
|
|
@@ -69068,7 +69279,7 @@ var reportSweepsCheck = {
|
|
|
69068
69279
|
const graded = gradedReportMonths(reportMonths, now);
|
|
69069
69280
|
const months = reportMonths.map((month) => {
|
|
69070
69281
|
const bounds = calendarMonthBounds(month);
|
|
69071
|
-
const monthRuns = ctx.db.select().from(runs).where(
|
|
69282
|
+
const monthRuns = ctx.db.select().from(runs).where(and81(eq92(runs.projectId, ctx.project.id), eq92(runs.kind, RunKinds["answer-visibility"]), notProbeRun(), gte26(runs.createdAt, bounds.since), lte27(runs.createdAt, bounds.until))).orderBy(asc24(runs.createdAt), asc24(runs.id)).all();
|
|
69072
69283
|
const evidence = monthRuns.map((run) => inspectSweep(ctx, run));
|
|
69073
69284
|
return {
|
|
69074
69285
|
month,
|
|
@@ -69091,7 +69302,7 @@ var reportSweepsCheck = {
|
|
|
69091
69302
|
};
|
|
69092
69303
|
|
|
69093
69304
|
// ../api-routes/src/doctor/checks/report-models.ts
|
|
69094
|
-
import { and as
|
|
69305
|
+
import { and as and82, eq as eq93, gte as gte27, inArray as inArray42, lte as lte28 } from "drizzle-orm";
|
|
69095
69306
|
var reportModelsCheck = {
|
|
69096
69307
|
id: "report.models",
|
|
69097
69308
|
category: CheckCategories.providers,
|
|
@@ -69106,7 +69317,7 @@ var reportModelsCheck = {
|
|
|
69106
69317
|
const months = reportMonths.flatMap((month) => {
|
|
69107
69318
|
const bounds = calendarMonthBounds(month);
|
|
69108
69319
|
const previous = new Date(Date.parse(bounds.since) - 1).toISOString().slice(0, 7);
|
|
69109
|
-
const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs,
|
|
69320
|
+
const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq93(runs.id, querySnapshots.runId)).where(and82(eq93(runs.projectId, ctx.project.id), eq93(runs.kind, RunKinds["answer-visibility"]), inArray42(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte27(runs.createdAt, bounds.since), lte28(runs.createdAt, bounds.until))).all();
|
|
69110
69321
|
return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
|
|
69111
69322
|
month,
|
|
69112
69323
|
previousMonth: previous,
|
|
@@ -69147,7 +69358,7 @@ var reportModelsCheck = {
|
|
|
69147
69358
|
};
|
|
69148
69359
|
|
|
69149
69360
|
// ../api-routes/src/doctor/checks/report-daily-data.ts
|
|
69150
|
-
import { eq as
|
|
69361
|
+
import { eq as eq94 } from "drizzle-orm";
|
|
69151
69362
|
var REPORTING_LAG_DAYS = 3;
|
|
69152
69363
|
function dateSpan(dates) {
|
|
69153
69364
|
const sorted = [...dates].sort();
|
|
@@ -69156,31 +69367,31 @@ function dateSpan(dates) {
|
|
|
69156
69367
|
function latestSyncRange(ctx, source) {
|
|
69157
69368
|
const projectId = ctx.project.id;
|
|
69158
69369
|
if (source === "ga") {
|
|
69159
|
-
const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(
|
|
69370
|
+
const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq94(gaDailyTotals.projectId, projectId)).all();
|
|
69160
69371
|
const latest2 = rows2.reduce((max2, row) => max2 === null || row.syncedAt > max2 ? row.syncedAt : max2, null);
|
|
69161
|
-
const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
69372
|
+
const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq94(gaTrafficSummaries.projectId, projectId)).get();
|
|
69162
69373
|
if (summary3 && (latest2 === null || summary3.syncedAt >= latest2)) return { start: summary3.start, end: summary3.end, syncedAt: summary3.syncedAt };
|
|
69163
69374
|
const span2 = dateSpan(rows2.filter((row) => row.syncedAt === latest2).map((row) => row.date));
|
|
69164
69375
|
return span2 && latest2 !== null ? { ...span2, syncedAt: latest2 } : null;
|
|
69165
69376
|
}
|
|
69166
|
-
const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(
|
|
69377
|
+
const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq94(gscDailyTotals.projectId, projectId)).all();
|
|
69167
69378
|
const latest = rows.reduce((max2, row) => max2 === null || row.createdAt > max2 ? row.createdAt : max2, null);
|
|
69168
69379
|
const span = dateSpan(rows.filter((row) => row.createdAt === latest).map((row) => row.date));
|
|
69169
69380
|
if (!span || latest === null) return null;
|
|
69170
|
-
const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(
|
|
69381
|
+
const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq94(gscDataWatermarks.projectId, projectId)).get();
|
|
69171
69382
|
return watermark?.through && watermark.updatedAt === latest && watermark.through > span.end ? { start: span.start, end: watermark.through, syncedAt: latest } : { ...span, syncedAt: latest };
|
|
69172
69383
|
}
|
|
69173
69384
|
function dailyCoverage(ctx, month, source) {
|
|
69174
69385
|
const project = ctx.project;
|
|
69175
69386
|
const connection = source === "ga" ? ctx.ga4CredentialStore?.getConnection(project.name) ?? ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4") : ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "gsc");
|
|
69176
|
-
const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(
|
|
69387
|
+
const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(eq94(gaDailyTotals.projectId, project.id)).all() : ctx.db.select({ date: gscDailyTotals.date, count: gscDailyTotals.impressions }).from(gscDailyTotals).where(eq94(gscDailyTotals.projectId, project.id)).all();
|
|
69177
69388
|
if (!connection && stored.length === 0) return null;
|
|
69178
69389
|
const bounds = calendarMonthBounds(month);
|
|
69179
69390
|
const start = bounds.since.slice(0, 10);
|
|
69180
69391
|
const end = bounds.until.slice(0, 10);
|
|
69181
69392
|
const timeZone = source === "gsc" ? "America/Los_Angeles" : "UTC";
|
|
69182
69393
|
const matureThrough = isoDateDaysBeforeInTimeZone((/* @__PURE__ */ new Date()).toISOString(), REPORTING_LAG_DAYS, timeZone);
|
|
69183
|
-
const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(
|
|
69394
|
+
const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq94(projects.id, project.id)).get().date.slice(0, 10);
|
|
69184
69395
|
const connectedAt = connection?.createdAt?.slice(0, 10) ?? created;
|
|
69185
69396
|
const onboardingDate = [connectedAt, ...stored.map((row) => row.date)].sort()[0];
|
|
69186
69397
|
const byDate = new Map(stored.map((row) => [row.date, row.count]));
|
|
@@ -69262,7 +69473,7 @@ var reportDailyDataCheck = {
|
|
|
69262
69473
|
};
|
|
69263
69474
|
|
|
69264
69475
|
// ../api-routes/src/doctor/checks/referral-assessment.ts
|
|
69265
|
-
import { and as
|
|
69476
|
+
import { and as and83, eq as eq95, ne as ne16 } from "drizzle-orm";
|
|
69266
69477
|
var REFERRAL_ASSESSMENT_CHECKS = [{
|
|
69267
69478
|
id: "report.ai-referral-bursts",
|
|
69268
69479
|
category: CheckCategories.integrations,
|
|
@@ -69271,7 +69482,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
|
|
|
69271
69482
|
notificationPolicy: CheckNotificationPolicies.silent,
|
|
69272
69483
|
run: (ctx) => {
|
|
69273
69484
|
if (!ctx.project) return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.no-project", summary: "Select a project to inspect referral evidence." };
|
|
69274
|
-
const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(
|
|
69485
|
+
const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and83(eq95(trafficSources.projectId, ctx.project.id), ne16(trafficSources.status, TrafficSourceStatuses.archived))).get();
|
|
69275
69486
|
if (!configured) {
|
|
69276
69487
|
return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.not-configured", summary: "No non-archived server traffic source is configured for this project." };
|
|
69277
69488
|
}
|
|
@@ -69683,17 +69894,17 @@ var BACKLINKS_CHECKS = [
|
|
|
69683
69894
|
];
|
|
69684
69895
|
|
|
69685
69896
|
// ../api-routes/src/doctor/checks/bing-auth.ts
|
|
69686
|
-
import { and as
|
|
69897
|
+
import { and as and84, desc as desc42, eq as eq96, inArray as inArray43 } from "drizzle-orm";
|
|
69687
69898
|
var BING_CONNECTION_ENTITY = "bing_connection";
|
|
69688
69899
|
var BING_CONNECTED_ACTION = "bing.connected";
|
|
69689
69900
|
var BING_DISCONNECTED_ACTION = "bing.disconnected";
|
|
69690
69901
|
function lostBingConnection(ctx, project) {
|
|
69691
69902
|
const hasRow = (row) => row !== void 0;
|
|
69692
|
-
const storedData = hasRow(ctx.db.select({ id: bingCoverageSnapshots.id }).from(bingCoverageSnapshots).where(
|
|
69903
|
+
const storedData = hasRow(ctx.db.select({ id: bingCoverageSnapshots.id }).from(bingCoverageSnapshots).where(eq96(bingCoverageSnapshots.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingUrlInspections.id }).from(bingUrlInspections).where(eq96(bingUrlInspections.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingKeywordStats.id }).from(bingKeywordStats).where(eq96(bingKeywordStats.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingTrafficDaily.id }).from(bingTrafficDaily).where(eq96(bingTrafficDaily.projectId, project.id)).limit(1).get());
|
|
69693
69904
|
if (!storedData) return false;
|
|
69694
|
-
const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(
|
|
69695
|
-
|
|
69696
|
-
|
|
69905
|
+
const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and84(
|
|
69906
|
+
eq96(auditLog.entityType, BING_CONNECTION_ENTITY),
|
|
69907
|
+
eq96(auditLog.entityId, project.canonicalDomain),
|
|
69697
69908
|
inArray43(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
|
|
69698
69909
|
)).orderBy(desc42(auditLog.createdAt)).limit(1).get();
|
|
69699
69910
|
return lastChange?.action !== BING_DISCONNECTED_ACTION;
|
|
@@ -69852,7 +70063,7 @@ var BING_AUTH_CHECKS = [
|
|
|
69852
70063
|
];
|
|
69853
70064
|
|
|
69854
70065
|
// ../api-routes/src/doctor/checks/content.ts
|
|
69855
|
-
import { eq as
|
|
70066
|
+
import { eq as eq97 } from "drizzle-orm";
|
|
69856
70067
|
var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
|
|
69857
70068
|
var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
|
|
69858
70069
|
function skippedNoProject2() {
|
|
@@ -69865,7 +70076,7 @@ function skippedNoProject2() {
|
|
|
69865
70076
|
}
|
|
69866
70077
|
function loadProject(ctx) {
|
|
69867
70078
|
if (!ctx.project) return null;
|
|
69868
|
-
return ctx.db.select().from(projects).where(
|
|
70079
|
+
return ctx.db.select().from(projects).where(eq97(projects.id, ctx.project.id)).get() ?? null;
|
|
69869
70080
|
}
|
|
69870
70081
|
var winnabilityCoverageCheck = {
|
|
69871
70082
|
id: "content.winnability.coverage",
|
|
@@ -69958,7 +70169,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
|
|
|
69958
70169
|
);
|
|
69959
70170
|
|
|
69960
70171
|
// ../api-routes/src/doctor/checks/ads.ts
|
|
69961
|
-
import { eq as
|
|
70172
|
+
import { eq as eq98 } from "drizzle-orm";
|
|
69962
70173
|
var ADS_RECENT_SYNC_WARN_DAYS = 7;
|
|
69963
70174
|
var ADS_RECENT_SYNC_FAIL_DAYS = 30;
|
|
69964
70175
|
var adsConnectionCheck = {
|
|
@@ -69975,7 +70186,7 @@ var adsConnectionCheck = {
|
|
|
69975
70186
|
remediation: null
|
|
69976
70187
|
};
|
|
69977
70188
|
}
|
|
69978
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
70189
|
+
const row = ctx.db.select().from(adsConnections).where(eq98(adsConnections.projectId, ctx.project.id)).get();
|
|
69979
70190
|
if (!row) {
|
|
69980
70191
|
return {
|
|
69981
70192
|
status: CheckStatuses.skipped,
|
|
@@ -70025,7 +70236,7 @@ var adsRecentSyncCheck = {
|
|
|
70025
70236
|
remediation: null
|
|
70026
70237
|
};
|
|
70027
70238
|
}
|
|
70028
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
70239
|
+
const row = ctx.db.select().from(adsConnections).where(eq98(adsConnections.projectId, ctx.project.id)).get();
|
|
70029
70240
|
if (!row) {
|
|
70030
70241
|
return {
|
|
70031
70242
|
status: CheckStatuses.skipped,
|
|
@@ -70240,7 +70451,7 @@ var ga4ConnectionCheck = {
|
|
|
70240
70451
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
70241
70452
|
|
|
70242
70453
|
// ../api-routes/src/doctor/checks/data-freshness.ts
|
|
70243
|
-
import { and as
|
|
70454
|
+
import { and as and85, desc as desc43, eq as eq99, inArray as inArray44 } from "drizzle-orm";
|
|
70244
70455
|
var GA_DATA_AGING_DAYS = 3;
|
|
70245
70456
|
var GA_DATA_STALE_DAYS = 5;
|
|
70246
70457
|
var GSC_DATA_AGING_DAYS = 5;
|
|
@@ -70270,7 +70481,7 @@ function skipped(code, summary3) {
|
|
|
70270
70481
|
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
70271
70482
|
}
|
|
70272
70483
|
function lastSyncAgeDays(ctx, kinds) {
|
|
70273
|
-
const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(
|
|
70484
|
+
const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and85(eq99(runs.projectId, ctx.project.id), inArray44(runs.kind, kinds), eq99(runs.status, "completed"))).orderBy(desc43(runs.createdAt)).limit(1).get();
|
|
70274
70485
|
if (!row?.createdAt) return null;
|
|
70275
70486
|
const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS6);
|
|
70276
70487
|
return Number.isFinite(age) ? age : null;
|
|
@@ -70372,7 +70583,7 @@ var gaRecentDataCheck = {
|
|
|
70372
70583
|
const connection = gaConnected(ctx);
|
|
70373
70584
|
if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
|
|
70374
70585
|
if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
|
|
70375
|
-
const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(
|
|
70586
|
+
const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq99(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc43(gaDailyTotals.date)).limit(1).get();
|
|
70376
70587
|
return grade(ctx, GA_SPEC, newest?.date);
|
|
70377
70588
|
}
|
|
70378
70589
|
};
|
|
@@ -70387,16 +70598,16 @@ var gscRecentDataCheck = {
|
|
|
70387
70598
|
if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
|
|
70388
70599
|
return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
|
|
70389
70600
|
}
|
|
70390
|
-
const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(
|
|
70601
|
+
const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq99(gscDataWatermarks.projectId, ctx.project.id)).get();
|
|
70391
70602
|
if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
|
|
70392
|
-
const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(
|
|
70603
|
+
const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq99(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc43(gscDailyTotals.date)).limit(1).get();
|
|
70393
70604
|
return grade(ctx, GSC_SPEC, newest?.date);
|
|
70394
70605
|
}
|
|
70395
70606
|
};
|
|
70396
70607
|
var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
|
|
70397
70608
|
|
|
70398
70609
|
// ../api-routes/src/doctor/checks/seo-refresh.ts
|
|
70399
|
-
import { and as
|
|
70610
|
+
import { and as and86, asc as asc25, desc as desc44, eq as eq100, gt as gt8, inArray as inArray45, sql as sql41 } from "drizzle-orm";
|
|
70400
70611
|
var GRACE_DAYS = 2;
|
|
70401
70612
|
var DAY_MS7 = 24 * 60 * 60 * 1e3;
|
|
70402
70613
|
var FIRING_SPAN_MS = 6e4;
|
|
@@ -70405,7 +70616,7 @@ function skipped2(code, summary3) {
|
|
|
70405
70616
|
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
70406
70617
|
}
|
|
70407
70618
|
function seoSyncRuns(projectId) {
|
|
70408
|
-
return
|
|
70619
|
+
return and86(eq100(runs.projectId, projectId), eq100(runs.kind, RunKinds["seo-sync"]));
|
|
70409
70620
|
}
|
|
70410
70621
|
function scheduleTiming(schedule) {
|
|
70411
70622
|
if (!schedule.recurrence) return { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence: null };
|
|
@@ -70413,16 +70624,16 @@ function scheduleTiming(schedule) {
|
|
|
70413
70624
|
return recurrence ? { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence } : null;
|
|
70414
70625
|
}
|
|
70415
70626
|
function latestFiring(ctx, projectId, enabledSince) {
|
|
70416
|
-
const scheduled =
|
|
70627
|
+
const scheduled = and86(seoSyncRuns(projectId), eq100(runs.trigger, RunTriggers.scheduled), gt8(runs.createdAt, enabledSince));
|
|
70417
70628
|
const newest = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(scheduled).orderBy(desc44(runs.createdAt)).limit(1).get();
|
|
70418
70629
|
if (!newest) return [];
|
|
70419
70630
|
const newestMs = Date.parse(newest.createdAt);
|
|
70420
70631
|
const after = Number.isFinite(newestMs) ? new Date(newestMs - FIRING_SPAN_MS).toISOString() : newest.createdAt;
|
|
70421
|
-
return ctx.db.select({ id: runs.id, status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(
|
|
70632
|
+
return ctx.db.select({ id: runs.id, status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and86(scheduled, gt8(runs.createdAt, after))).orderBy(asc25(runs.createdAt), asc25(runs.id)).all();
|
|
70422
70633
|
}
|
|
70423
70634
|
function lastCompleted(ctx, projectId) {
|
|
70424
70635
|
const at = sql41`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
|
|
70425
|
-
return ctx.db.select({ id: runs.id, at }).from(runs).where(
|
|
70636
|
+
return ctx.db.select({ id: runs.id, at }).from(runs).where(and86(seoSyncRuns(projectId), eq100(runs.status, RunStatuses.completed))).orderBy(desc44(at)).limit(1).get() ?? null;
|
|
70426
70637
|
}
|
|
70427
70638
|
function isIncomplete(run) {
|
|
70428
70639
|
return run.status === RunStatuses.failed || run.status === RunStatuses.partial;
|
|
@@ -70436,7 +70647,7 @@ function unsettledRuns(ctx, projectId, firing, profiles) {
|
|
|
70436
70647
|
const incomplete = firing.filter(isIncomplete);
|
|
70437
70648
|
const firedAt = firing[firing.length - 1]?.createdAt;
|
|
70438
70649
|
if (incomplete.length === 0 || !firedAt) return incomplete;
|
|
70439
|
-
const refreshed = new Set(ctx.db.select({ profileId: seoJobs.profileId }).from(runs).innerJoin(seoJobs,
|
|
70650
|
+
const refreshed = new Set(ctx.db.select({ profileId: seoJobs.profileId }).from(runs).innerJoin(seoJobs, eq100(seoJobs.runId, runs.id)).where(and86(seoSyncRuns(projectId), eq100(runs.status, RunStatuses.completed), gt8(runs.createdAt, firedAt))).all().map((row) => row.profileId));
|
|
70440
70651
|
return incomplete.filter((run) => {
|
|
70441
70652
|
const profile = profiles.get(run.id) ?? null;
|
|
70442
70653
|
return profile === null ? refreshed.size === 0 : !refreshed.has(profile);
|
|
@@ -70446,7 +70657,7 @@ var COUNT_LIST = new Intl.ListFormat("en", { style: "long", type: "conjunction"
|
|
|
70446
70657
|
function refreshReason(ctx, run) {
|
|
70447
70658
|
const stored = parseRunError(run.error);
|
|
70448
70659
|
if (stored) return formatRunErrorOneLine(stored);
|
|
70449
|
-
const tasks = currentSeoTasks(ctx.db.select({ id: seoTasks.id, retryOf: seoTasks.retryOf, status: seoTasks.status, error: seoTasks.error, request: seoTasks.request }).from(seoTasks).innerJoin(seoJobs,
|
|
70660
|
+
const tasks = currentSeoTasks(ctx.db.select({ id: seoTasks.id, retryOf: seoTasks.retryOf, status: seoTasks.status, error: seoTasks.error, request: seoTasks.request }).from(seoTasks).innerJoin(seoJobs, eq100(seoTasks.jobId, seoJobs.id)).where(eq100(seoJobs.runId, run.id)).all());
|
|
70450
70661
|
const failed = tasks.filter((task) => task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"]);
|
|
70451
70662
|
const partial = tasks.filter((task) => task.status === SeoRunStatuses.partial).length;
|
|
70452
70663
|
const cancelled = tasks.filter((task) => task.status === SeoRunStatuses.cancelled).length;
|
|
@@ -70466,7 +70677,7 @@ function refreshReason(ctx, run) {
|
|
|
70466
70677
|
return `Of ${tasks.length} ${unit}, ${COUNT_LIST.format(counts2)}.${errors ? ` ${errors}` : ""}`;
|
|
70467
70678
|
}
|
|
70468
70679
|
function gradeSeoRefresh(ctx, project) {
|
|
70469
|
-
const schedule = ctx.db.select().from(schedules).where(
|
|
70680
|
+
const schedule = ctx.db.select().from(schedules).where(and86(eq100(schedules.projectId, project.id), eq100(schedules.kind, SchedulableRunKinds["seo-sync"]))).get();
|
|
70470
70681
|
if (!schedule) return skipped2("seo.data.not-scheduled", "No SEO refresh schedule is set for this project.");
|
|
70471
70682
|
if (!schedule.enabled) return skipped2("seo.data.not-scheduled", "The SEO refresh schedule is disabled.");
|
|
70472
70683
|
const name = project.name;
|
|
@@ -70538,7 +70749,7 @@ function gradeSeoRefresh(ctx, project) {
|
|
|
70538
70749
|
};
|
|
70539
70750
|
}
|
|
70540
70751
|
if (now >= Date.parse(deadlineAt)) {
|
|
70541
|
-
const active = ctx.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
70752
|
+
const active = ctx.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and86(seoSyncRuns(project.id), inArray45(runs.status, [RunStatuses.queued, RunStatuses.running]))).orderBy(asc25(runs.createdAt)).limit(1).get();
|
|
70542
70753
|
const since = last ? `No SEO refresh has completed since ${day(last.at)}.` : "No SEO refresh has completed since the schedule was enabled.";
|
|
70543
70754
|
const missed = last ? `The scheduled refresh due ${day(dueAt)} did not complete within ${GRACE_DAYS} days.` : `Its first refresh, due ${day(dueAt)}, did not complete within ${GRACE_DAYS} days.`;
|
|
70544
70755
|
return {
|
|
@@ -70848,7 +71059,7 @@ var siteReachabilityCheck = {
|
|
|
70848
71059
|
var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
|
|
70849
71060
|
|
|
70850
71061
|
// ../api-routes/src/doctor/checks/api-hosts.ts
|
|
70851
|
-
import { eq as
|
|
71062
|
+
import { eq as eq102 } from "drizzle-orm";
|
|
70852
71063
|
|
|
70853
71064
|
// ../api-routes/src/api-host-probe.ts
|
|
70854
71065
|
import dns4 from "dns/promises";
|
|
@@ -70954,7 +71165,7 @@ async function probeApiHost(host, options = {}) {
|
|
|
70954
71165
|
}
|
|
70955
71166
|
|
|
70956
71167
|
// ../api-routes/src/doctor/checks/integration-sync.ts
|
|
70957
|
-
import { and as
|
|
71168
|
+
import { and as and87, count as count7, desc as desc45, eq as eq101, gte as gte28, gt as gt9, inArray as inArray46, isNull as isNull14, notInArray as notInArray3, or as or19 } from "drizzle-orm";
|
|
70958
71169
|
var SYNC_FAILURE_WINDOW = 3;
|
|
70959
71170
|
var SUMMARY_ERROR_LIMIT = 300;
|
|
70960
71171
|
var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
|
|
@@ -70981,7 +71192,7 @@ function gscConnection(ctx, project) {
|
|
|
70981
71192
|
function gbpConnection(ctx, project) {
|
|
70982
71193
|
if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
|
|
70983
71194
|
if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
|
|
70984
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(
|
|
71195
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and87(eq101(gbpLocations.projectId, project.id), eq101(gbpLocations.selected, true))).limit(1).get();
|
|
70985
71196
|
return selected ? CONNECTED : { state: "no-selected-locations" };
|
|
70986
71197
|
}
|
|
70987
71198
|
function gaConnection(ctx, project) {
|
|
@@ -71054,16 +71265,16 @@ function latestErrorText(raw) {
|
|
|
71054
71265
|
return parsed ? formatRunErrorOneLine(parsed) : "no error was recorded";
|
|
71055
71266
|
}
|
|
71056
71267
|
function readSyncRunState(db, projectId, kind, options = {}) {
|
|
71057
|
-
const scope =
|
|
71058
|
-
|
|
71059
|
-
|
|
71060
|
-
...options.sourceId ? [
|
|
71268
|
+
const scope = and87(
|
|
71269
|
+
eq101(runs.projectId, projectId),
|
|
71270
|
+
eq101(runs.kind, kind),
|
|
71271
|
+
...options.sourceId ? [eq101(runs.sourceId, options.sourceId)] : [],
|
|
71061
71272
|
...options.nothingToSyncErrors?.length ? [or19(isNull14(runs.error), notInArray3(runs.error, [...options.nothingToSyncErrors]))] : []
|
|
71062
71273
|
);
|
|
71063
|
-
const graded = options.since ?
|
|
71064
|
-
const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(
|
|
71065
|
-
const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
71066
|
-
const failureCount = db.select({ count: count7() }).from(runs).where(
|
|
71274
|
+
const graded = options.since ? and87(scope, gte28(runs.createdAt, options.since)) : scope;
|
|
71275
|
+
const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and87(graded, inArray46(runs.status, FINISHED_STATUSES))).orderBy(desc45(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
|
|
71276
|
+
const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and87(graded, inArray46(runs.status, SUCCESS_STATUSES))).orderBy(desc45(runs.createdAt)).limit(1).get();
|
|
71277
|
+
const failureCount = db.select({ count: count7() }).from(runs).where(and87(lastSuccess ? and87(graded, gt9(runs.createdAt, lastSuccess.createdAt)) : graded, eq101(runs.status, RunStatuses.failed))).get();
|
|
71067
71278
|
const failuresSinceSuccess = Number(failureCount?.count ?? 0);
|
|
71068
71279
|
const latest = recent[0];
|
|
71069
71280
|
return {
|
|
@@ -71160,7 +71371,7 @@ function connectionFor(prefix, ctx, project) {
|
|
|
71160
71371
|
if (prefix === "google-ads") return googleMarketingConnection("googleAds")(ctx);
|
|
71161
71372
|
if (prefix === "gtm") return googleMarketingConnection("gtm")(ctx);
|
|
71162
71373
|
if (prefix === "bing") return ctx.bingConnectionStore?.getConnection(project.canonicalDomain) ? { state: "connected", since: null } : { state: "not-connected" };
|
|
71163
|
-
return ctx.db.select({ id: adsConnections.id }).from(adsConnections).where(
|
|
71374
|
+
return ctx.db.select({ id: adsConnections.id }).from(adsConnections).where(eq102(adsConnections.projectId, project.id)).limit(1).get() ? { state: "connected", since: null } : { state: "not-connected" };
|
|
71164
71375
|
}
|
|
71165
71376
|
function syncCommand(prefix) {
|
|
71166
71377
|
if (prefix === "gsc") return "google sync";
|
|
@@ -71205,7 +71416,7 @@ function definition(prefix) {
|
|
|
71205
71416
|
var API_HOST_CHECKS = Object.keys(API_HOST_TABLE).map(definition);
|
|
71206
71417
|
|
|
71207
71418
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
71208
|
-
import { and as
|
|
71419
|
+
import { and as and88, eq as eq103 } from "drizzle-orm";
|
|
71209
71420
|
var RECENT_SYNC_WARN_DAYS = 4;
|
|
71210
71421
|
var RECENT_SYNC_FAIL_DAYS = 30;
|
|
71211
71422
|
function skippedNoProject3() {
|
|
@@ -71225,7 +71436,7 @@ function storeUnavailable() {
|
|
|
71225
71436
|
};
|
|
71226
71437
|
}
|
|
71227
71438
|
function hasSelectedLocation(ctx, projectId) {
|
|
71228
|
-
return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(
|
|
71439
|
+
return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and88(eq103(gbpLocations.projectId, projectId), eq103(gbpLocations.selected, true))).limit(1).get();
|
|
71229
71440
|
}
|
|
71230
71441
|
async function resolveGbpToken(ctx) {
|
|
71231
71442
|
if (!ctx.project) return { ok: false, output: skippedNoProject3() };
|
|
@@ -71485,7 +71696,7 @@ var recentSyncCheck = {
|
|
|
71485
71696
|
title: "GBP recent sync",
|
|
71486
71697
|
run: (ctx) => {
|
|
71487
71698
|
if (!ctx.project) return skippedNoProject3();
|
|
71488
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
71699
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and88(eq103(gbpLocations.projectId, ctx.project.id), eq103(gbpLocations.selected, true))).all();
|
|
71489
71700
|
if (selected.length === 0) {
|
|
71490
71701
|
return {
|
|
71491
71702
|
status: CheckStatuses.skipped,
|
|
@@ -71545,7 +71756,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
71545
71756
|
);
|
|
71546
71757
|
|
|
71547
71758
|
// ../api-routes/src/doctor/checks/places.ts
|
|
71548
|
-
import { eq as
|
|
71759
|
+
import { eq as eq104 } from "drizzle-orm";
|
|
71549
71760
|
var apiKeyCheck = {
|
|
71550
71761
|
id: "gbp.places.api-key",
|
|
71551
71762
|
category: CheckCategories.auth,
|
|
@@ -71590,7 +71801,7 @@ var apiKeyCheck = {
|
|
|
71590
71801
|
details: { tier: cfg.tier }
|
|
71591
71802
|
};
|
|
71592
71803
|
}
|
|
71593
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
71804
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq104(gbpLocations.projectId, ctx.project.id)).all();
|
|
71594
71805
|
const selected = rows.filter((r) => r.selected);
|
|
71595
71806
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
71596
71807
|
const details = {
|
|
@@ -71623,7 +71834,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
|
|
|
71623
71834
|
);
|
|
71624
71835
|
|
|
71625
71836
|
// ../api-routes/src/doctor/checks/gbp-reviews.ts
|
|
71626
|
-
import { and as
|
|
71837
|
+
import { and as and89, desc as desc46, eq as eq105, gte as gte29, inArray as inArray47, sql as sql42 } from "drizzle-orm";
|
|
71627
71838
|
var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
|
|
71628
71839
|
var V4_ACCESS_REMEDIATION = "Business Profile reviews use the legacy My Business API (mybusiness.googleapis.com), which Google enables per Cloud project only on request; the API Library and `gcloud services enable` cannot turn it on. Open the enable link in the API access approval email as the approved account, or reply to the access-request thread asking Google to enable mybusiness.googleapis.com for your project number, then run `canonry gbp sync`.";
|
|
71629
71840
|
var reviewsAccessCheck = {
|
|
@@ -71651,7 +71862,7 @@ var reviewsAccessCheck = {
|
|
|
71651
71862
|
reviewsAccess: gbpLocations.reviewsAccess,
|
|
71652
71863
|
reviewsAccessReason: gbpLocations.reviewsAccessReason,
|
|
71653
71864
|
reviewsCheckedAt: gbpLocations.reviewsCheckedAt
|
|
71654
|
-
}).from(gbpLocations).where(
|
|
71865
|
+
}).from(gbpLocations).where(and89(eq105(gbpLocations.projectId, project.id), eq105(gbpLocations.selected, true))).all();
|
|
71655
71866
|
if (locations.length === 0) {
|
|
71656
71867
|
return {
|
|
71657
71868
|
status: CheckStatuses.skipped,
|
|
@@ -71671,7 +71882,7 @@ var reviewsAccessCheck = {
|
|
|
71671
71882
|
const places = ctx.getPlacesConfig?.();
|
|
71672
71883
|
const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
|
|
71673
71884
|
const v4Worked = new Set(
|
|
71674
|
-
ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(
|
|
71885
|
+
ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and89(eq105(gbpReviewRatings.projectId, project.id), eq105(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
|
|
71675
71886
|
);
|
|
71676
71887
|
const coverageOf = (loc) => {
|
|
71677
71888
|
if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
|
|
@@ -71705,7 +71916,7 @@ var reviewsAccessCheck = {
|
|
|
71705
71916
|
none > 0 ? `${none} have no review source` : null
|
|
71706
71917
|
].filter((part) => part !== null);
|
|
71707
71918
|
const summary3 = `Business Profile reviews are not available${why}. ${parts.join("; ")}.`;
|
|
71708
|
-
const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(
|
|
71919
|
+
const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq105(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
|
|
71709
71920
|
const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
|
|
71710
71921
|
if (none > 0 && subscribed) {
|
|
71711
71922
|
return {
|
|
@@ -71744,8 +71955,8 @@ var reviewDeliveryCheck = {
|
|
|
71744
71955
|
attempts: reviewWebhookDeliveries.attempts,
|
|
71745
71956
|
lastTouched,
|
|
71746
71957
|
payload: reviewWebhookDeliveries.payload
|
|
71747
|
-
}).from(reviewWebhookDeliveries).where(
|
|
71748
|
-
|
|
71958
|
+
}).from(reviewWebhookDeliveries).where(and89(
|
|
71959
|
+
eq105(reviewWebhookDeliveries.projectId, ctx.project.id),
|
|
71749
71960
|
inArray47(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
|
|
71750
71961
|
gte29(lastTouched, since)
|
|
71751
71962
|
)).orderBy(desc46(lastTouched)).all();
|
|
@@ -71757,7 +71968,7 @@ var reviewDeliveryCheck = {
|
|
|
71757
71968
|
remediation: null
|
|
71758
71969
|
};
|
|
71759
71970
|
}
|
|
71760
|
-
const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(
|
|
71971
|
+
const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(eq105(notifications.projectId, ctx.project.id)).all().map((row) => [row.id, redactNotificationUrl(row.config.url).urlDisplay]));
|
|
71761
71972
|
const deliveries = rows.map((row) => {
|
|
71762
71973
|
const review = row.payload.reviews[0];
|
|
71763
71974
|
return {
|
|
@@ -72331,7 +72542,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
72331
72542
|
];
|
|
72332
72543
|
|
|
72333
72544
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
72334
|
-
import { and as
|
|
72545
|
+
import { and as and90, desc as desc47, eq as eq106, gte as gte30, inArray as inArray48, isNotNull as isNotNull9, ne as ne17, sql as sql43 } from "drizzle-orm";
|
|
72335
72546
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
72336
72547
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
72337
72548
|
function isCloudflareDirectPush(source) {
|
|
@@ -72370,8 +72581,8 @@ function skippedNoProject5() {
|
|
|
72370
72581
|
function loadProbes(ctx) {
|
|
72371
72582
|
if (!ctx.project) return [];
|
|
72372
72583
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
72373
|
-
|
|
72374
|
-
|
|
72584
|
+
and90(
|
|
72585
|
+
eq106(trafficSources.projectId, ctx.project.id),
|
|
72375
72586
|
ne17(trafficSources.status, TrafficSourceStatuses.archived)
|
|
72376
72587
|
)
|
|
72377
72588
|
).all();
|
|
@@ -72509,8 +72720,8 @@ var recentDataCheck = {
|
|
|
72509
72720
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
72510
72721
|
const recentCrawlers = Number(
|
|
72511
72722
|
ctx.db.select({ total: sql43`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
72512
|
-
|
|
72513
|
-
|
|
72723
|
+
and90(
|
|
72724
|
+
eq106(crawlerEventsHourly.projectId, ctx.project.id),
|
|
72514
72725
|
inArray48(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
72515
72726
|
gte30(crawlerEventsHourly.tsHour, warnCutoff)
|
|
72516
72727
|
)
|
|
@@ -72518,8 +72729,8 @@ var recentDataCheck = {
|
|
|
72518
72729
|
);
|
|
72519
72730
|
const recentReferrals = Number(
|
|
72520
72731
|
ctx.db.select({ total: sql43`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
72521
|
-
|
|
72522
|
-
|
|
72732
|
+
and90(
|
|
72733
|
+
eq106(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
72523
72734
|
inArray48(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
72524
72735
|
gte30(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
72525
72736
|
)
|
|
@@ -72527,8 +72738,8 @@ var recentDataCheck = {
|
|
|
72527
72738
|
);
|
|
72528
72739
|
const recentUserFetches = Number(
|
|
72529
72740
|
ctx.db.select({ total: sql43`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
72530
|
-
|
|
72531
|
-
|
|
72741
|
+
and90(
|
|
72742
|
+
eq106(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
72532
72743
|
inArray48(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
72533
72744
|
gte30(aiUserFetchEventsHourly.tsHour, warnCutoff)
|
|
72534
72745
|
)
|
|
@@ -72553,8 +72764,8 @@ var recentDataCheck = {
|
|
|
72553
72764
|
}
|
|
72554
72765
|
const olderCrawlers = Number(
|
|
72555
72766
|
ctx.db.select({ total: sql43`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
72556
|
-
|
|
72557
|
-
|
|
72767
|
+
and90(
|
|
72768
|
+
eq106(crawlerEventsHourly.projectId, ctx.project.id),
|
|
72558
72769
|
inArray48(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
72559
72770
|
gte30(crawlerEventsHourly.tsHour, failCutoff)
|
|
72560
72771
|
)
|
|
@@ -72562,8 +72773,8 @@ var recentDataCheck = {
|
|
|
72562
72773
|
);
|
|
72563
72774
|
const olderReferrals = Number(
|
|
72564
72775
|
ctx.db.select({ total: sql43`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
72565
|
-
|
|
72566
|
-
|
|
72776
|
+
and90(
|
|
72777
|
+
eq106(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
72567
72778
|
inArray48(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
72568
72779
|
gte30(aiReferralEventsHourly.tsHour, failCutoff)
|
|
72569
72780
|
)
|
|
@@ -72571,8 +72782,8 @@ var recentDataCheck = {
|
|
|
72571
72782
|
);
|
|
72572
72783
|
const olderUserFetches = Number(
|
|
72573
72784
|
ctx.db.select({ total: sql43`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
72574
|
-
|
|
72575
|
-
|
|
72785
|
+
and90(
|
|
72786
|
+
eq106(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
72576
72787
|
inArray48(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
72577
72788
|
gte30(aiUserFetchEventsHourly.tsHour, failCutoff)
|
|
72578
72789
|
)
|
|
@@ -72899,10 +73110,10 @@ var syncLagCheck = {
|
|
|
72899
73110
|
}
|
|
72900
73111
|
};
|
|
72901
73112
|
function lastQueueStopReason(ctx, sourceId) {
|
|
72902
|
-
const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(
|
|
72903
|
-
|
|
72904
|
-
|
|
72905
|
-
|
|
73113
|
+
const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and90(
|
|
73114
|
+
eq106(runs.projectId, ctx.project.id),
|
|
73115
|
+
eq106(runs.kind, RunKinds["traffic-sync"]),
|
|
73116
|
+
eq106(runs.sourceId, sourceId),
|
|
72906
73117
|
isNotNull9(runs.syncSummary)
|
|
72907
73118
|
)).orderBy(desc47(runs.createdAt)).limit(1).get();
|
|
72908
73119
|
return row?.syncSummary?.type === "cloudflare-queue" ? row.syncSummary.stopReason : null;
|
|
@@ -73702,7 +73913,7 @@ async function doctorRoutes(app, opts) {
|
|
|
73702
73913
|
}
|
|
73703
73914
|
|
|
73704
73915
|
// ../api-routes/src/status.ts
|
|
73705
|
-
import { and as
|
|
73916
|
+
import { and as and91, desc as desc48, eq as eq107, isNull as isNull15 } from "drizzle-orm";
|
|
73706
73917
|
function ageDays(iso2) {
|
|
73707
73918
|
if (!iso2) return null;
|
|
73708
73919
|
const ms = Date.now() - Date.parse(iso2);
|
|
@@ -73752,7 +73963,7 @@ function makeRow(integration, label, runKind, state, staleAfterDays, dataThrough
|
|
|
73752
73963
|
};
|
|
73753
73964
|
}
|
|
73754
73965
|
function latestDate2(db, table, projectId) {
|
|
73755
|
-
const row = db.select({ date: table.date }).from(table).where(
|
|
73966
|
+
const row = db.select({ date: table.date }).from(table).where(eq107(table.projectId, projectId)).orderBy(desc48(table.date)).limit(1).get();
|
|
73756
73967
|
return row?.date ?? null;
|
|
73757
73968
|
}
|
|
73758
73969
|
function projectRows(opts, project) {
|
|
@@ -73767,7 +73978,7 @@ function projectRows(opts, project) {
|
|
|
73767
73978
|
const rows = [];
|
|
73768
73979
|
const gsc = gscConnection(ctx, info);
|
|
73769
73980
|
if (connected(gsc)) {
|
|
73770
|
-
const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(
|
|
73981
|
+
const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq107(gscDataWatermarks.projectId, project.id)).limit(1).get();
|
|
73771
73982
|
const state = readSyncRunState(opts.db, project.id, RunKinds["gsc-sync"], { since: gsc.since });
|
|
73772
73983
|
const dataThrough = watermarkRow?.dataThroughDate ?? latestDate2(opts.db, gscDailyTotals, project.id);
|
|
73773
73984
|
rows.push(makeRow("gsc", "Search Console", RunKinds["gsc-sync"], state, SYNC_IDLE_DAYS, dataThrough, null, ["gsc.sync.recent-failures"], GSC_DATA_AGING_DAYS));
|
|
@@ -73783,12 +73994,12 @@ function projectRows(opts, project) {
|
|
|
73783
73994
|
rows.push(makeRow("bing", "Bing Webmaster Tools", RunKinds["bing-inspect-sitemap"], readSyncRunState(opts.db, project.id, RunKinds["bing-inspect-sitemap"]), null));
|
|
73784
73995
|
rows.push(makeRow("bing-search", "Bing search data", RunKinds["bing-sync"], readSyncRunState(opts.db, project.id, RunKinds["bing-sync"]), SYNC_IDLE_DAYS, latestDate2(opts.db, bingTrafficDaily, project.id), null, ["bing.sync.recent-failures"], BING_DATA_AGING_DAYS));
|
|
73785
73996
|
}
|
|
73786
|
-
const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(
|
|
73997
|
+
const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq107(adsConnections.projectId, project.id)).limit(1).get();
|
|
73787
73998
|
if (ads) rows.push(makeRow("ads", "OpenAI Ads", RunKinds["ads-sync"], readSyncRunState(opts.db, project.id, RunKinds["ads-sync"]), ADS_RECENT_SYNC_WARN_DAYS, null, null, ["ads.data.recent-sync"], ADS_RECENT_SYNC_WARN_DAYS, ads.lastSyncedAt));
|
|
73788
73999
|
const marketing = opts.getGoogleMarketingDoctorInput?.(ctx);
|
|
73789
74000
|
if (marketing?.googleAds) rows.push(makeRow("google-ads", "Google Ads", RunKinds["google-ads-sync"], readSyncRunState(opts.db, project.id, RunKinds["google-ads-sync"]), FRESH_SNAPSHOT_WARN_DAYS, null, null, ["google-ads.sync.recent-failures"], FRESH_SNAPSHOT_WARN_DAYS, marketing.googleAds.latestSnapshotAt));
|
|
73790
74001
|
if (marketing?.gtm) rows.push(makeRow("gtm", "Google Tag Manager", RunKinds["gtm-sync"], readSyncRunState(opts.db, project.id, RunKinds["gtm-sync"]), FRESH_SNAPSHOT_WARN_DAYS, null, null, ["gtm.sync.recent-failures"], FRESH_SNAPSHOT_WARN_DAYS, marketing.gtm.latestSnapshotAt));
|
|
73791
|
-
const sources = opts.db.select().from(trafficSources).where(
|
|
74002
|
+
const sources = opts.db.select().from(trafficSources).where(and91(eq107(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
|
|
73792
74003
|
for (const source of sources) {
|
|
73793
74004
|
const row = makeRow("traffic", source.displayName, RunKinds["traffic-sync"], readSyncRunState(opts.db, project.id, RunKinds["traffic-sync"], { sourceId: source.id }), null, null, source.id, ["traffic.source.sync-lag", "traffic.source.recent-data"]);
|
|
73794
74005
|
const lag = source.lastSyncedAt ? Date.now() - Date.parse(source.lastSyncedAt) : Number.POSITIVE_INFINITY;
|
|
@@ -73800,7 +74011,7 @@ function projectRows(opts, project) {
|
|
|
73800
74011
|
}
|
|
73801
74012
|
function projectStatus(opts, project) {
|
|
73802
74013
|
const integrations = projectRows(opts, project);
|
|
73803
|
-
const healthRow = opts.db.select().from(doctorHealthState).where(
|
|
74014
|
+
const healthRow = opts.db.select().from(doctorHealthState).where(eq107(doctorHealthState.projectId, project.id)).limit(1).get();
|
|
73804
74015
|
const health = healthRow ? { status: healthRow.status, code: healthRow.code, summary: healthRow.summary, checkedAt: healthRow.checkedAt, notifiedAt: healthRow.notifiedAt } : null;
|
|
73805
74016
|
const rank = { ok: 0, "never-synced": 1, warning: 2, stale: 3, failing: 4 };
|
|
73806
74017
|
const healthState = health?.status === "fail" ? "failing" : health?.status === "warn" ? "warning" : "ok";
|
|
@@ -73820,7 +74031,7 @@ async function statusRoutes(app, opts) {
|
|
|
73820
74031
|
if (request.query.problems === "true") result.projects = result.projects.map((item2) => ({ ...item2, integrations: item2.integrations.filter((row) => row.state !== "ok") }));
|
|
73821
74032
|
return instanceStatusDtoSchema.parse(result);
|
|
73822
74033
|
}
|
|
73823
|
-
const projectsRows = (scopedId ? opts.db.select().from(projects).where(
|
|
74034
|
+
const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq107(projects.id, scopedId)) : opts.db.select().from(projects)).all();
|
|
73824
74035
|
const output = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...opts.getMcpHealth ? { mcp: opts.getMcpHealth() } : {}, summary: { projects: projectsRows.length, ok: 0, failing: 0, warning: 0, stale: 0, neverSynced: 0 }, projects: projectsRows.map((project) => projectStatus(opts, project)) };
|
|
73825
74036
|
for (const project of output.projects) {
|
|
73826
74037
|
const key = project.state === "never-synced" ? "neverSynced" : project.state;
|
|
@@ -73833,7 +74044,7 @@ async function statusRoutes(app, opts) {
|
|
|
73833
74044
|
|
|
73834
74045
|
// ../api-routes/src/discovery/routes.ts
|
|
73835
74046
|
import crypto57 from "crypto";
|
|
73836
|
-
import { and as
|
|
74047
|
+
import { and as and92, desc as desc49, eq as eq108, gte as gte31, inArray as inArray49, isNull as isNull16, or as or20 } from "drizzle-orm";
|
|
73837
74048
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
73838
74049
|
async function discoveryRoutes(app, opts) {
|
|
73839
74050
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -73867,13 +74078,13 @@ async function discoveryRoutes(app, opts) {
|
|
|
73867
74078
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
73868
74079
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
73869
74080
|
const decision = app.db.transaction((tx) => {
|
|
73870
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
73871
|
-
|
|
73872
|
-
|
|
74081
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and92(
|
|
74082
|
+
eq108(discoverySessions.projectId, project.id),
|
|
74083
|
+
eq108(discoverySessions.icpDescription, icpDescription),
|
|
73873
74084
|
// Buyer is part of session identity: it changes the seed prompt's
|
|
73874
74085
|
// semantics, so a request with a different (or no) buyer must start
|
|
73875
74086
|
// its own session, never adopt another buyer's probes.
|
|
73876
|
-
parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) :
|
|
74087
|
+
parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq108(discoverySessions.buyerDescription, parsed.data.buyerDescription),
|
|
73877
74088
|
// Locations are identity too: a different service-area subset seeds
|
|
73878
74089
|
// and probes a different geo, so it must never reuse another geo's
|
|
73879
74090
|
// session. resolveLocations is deterministic (project-config order),
|
|
@@ -73883,11 +74094,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
73883
74094
|
// locations a NULL row's subset is unknowable, so it conservatively
|
|
73884
74095
|
// never reuses — a one-time, bounded (2h window) non-reuse after
|
|
73885
74096
|
// upgrade, never a wrong reuse.
|
|
73886
|
-
locations.length === 0 ? or20(isNull16(discoverySessions.locations),
|
|
74097
|
+
locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq108(discoverySessions.locations, locations)) : eq108(discoverySessions.locations, locations),
|
|
73887
74098
|
// Seed provider set is identity: a different phrasing distribution
|
|
73888
74099
|
// must never reuse another set's session. Null (= Gemini-only
|
|
73889
74100
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
73890
|
-
seedProviders == null ? isNull16(discoverySessions.seedProviders) :
|
|
74101
|
+
seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq108(discoverySessions.seedProviders, seedProviders),
|
|
73891
74102
|
inArray49(discoverySessions.status, [
|
|
73892
74103
|
DiscoverySessionStatuses.queued,
|
|
73893
74104
|
DiscoverySessionStatuses.seeding,
|
|
@@ -73963,7 +74174,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
73963
74174
|
const project = resolveProject(app.db, request.params.name);
|
|
73964
74175
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
73965
74176
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
73966
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
74177
|
+
const rows = app.db.select().from(discoverySessions).where(eq108(discoverySessions.projectId, project.id)).orderBy(desc49(discoverySessions.createdAt)).limit(limit).all();
|
|
73967
74178
|
return reply.send(rows.map(serializeSession));
|
|
73968
74179
|
}
|
|
73969
74180
|
);
|
|
@@ -73971,11 +74182,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
73971
74182
|
"/projects/:name/discover/sessions/:id",
|
|
73972
74183
|
async (request, reply) => {
|
|
73973
74184
|
const project = resolveProject(app.db, request.params.name);
|
|
73974
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
74185
|
+
const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
|
|
73975
74186
|
if (!session || session.projectId !== project.id) {
|
|
73976
74187
|
throw notFound("Discovery session", request.params.id);
|
|
73977
74188
|
}
|
|
73978
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
74189
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
|
|
73979
74190
|
const detail = {
|
|
73980
74191
|
...serializeSession(session),
|
|
73981
74192
|
probes: probeRows.map(serializeProbe)
|
|
@@ -73987,7 +74198,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
73987
74198
|
"/projects/:name/discover/sessions/:id/harvest",
|
|
73988
74199
|
async (request, reply) => {
|
|
73989
74200
|
const project = resolveProject(app.db, request.params.name);
|
|
73990
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
74201
|
+
const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
|
|
73991
74202
|
if (!session || session.projectId !== project.id) {
|
|
73992
74203
|
throw notFound("Discovery session", request.params.id);
|
|
73993
74204
|
}
|
|
@@ -73995,7 +74206,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
73995
74206
|
const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
|
|
73996
74207
|
const applyAnchor = request.query.anchor !== "false";
|
|
73997
74208
|
const provider = session.seedProvider ?? "gemini";
|
|
73998
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
74209
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
|
|
73999
74210
|
const extract = opts.harvestSearchQueries;
|
|
74000
74211
|
const probesWithQueries = probeRows.map((row) => {
|
|
74001
74212
|
if (!extract || !row.rawResponse) return { searchQueries: [] };
|
|
@@ -74006,7 +74217,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
74006
74217
|
return { searchQueries: [] };
|
|
74007
74218
|
}
|
|
74008
74219
|
});
|
|
74009
|
-
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(
|
|
74220
|
+
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq108(queries.projectId, project.id)).all().map((r) => r.query);
|
|
74010
74221
|
const anchorTerms = buildHarvestAnchorTerms(
|
|
74011
74222
|
[session.icpDescription ?? "", ...trackedQueries],
|
|
74012
74223
|
effectiveDomains(project)
|
|
@@ -74053,11 +74264,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
74053
74264
|
"/projects/:name/discover/sessions/:id/promote",
|
|
74054
74265
|
async (request, reply) => {
|
|
74055
74266
|
const project = resolveProject(app.db, request.params.name);
|
|
74056
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
74267
|
+
const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
|
|
74057
74268
|
if (!session || session.projectId !== project.id) {
|
|
74058
74269
|
throw notFound("Discovery session", request.params.id);
|
|
74059
74270
|
}
|
|
74060
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
74271
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
|
|
74061
74272
|
const cited = /* @__PURE__ */ new Set();
|
|
74062
74273
|
const aspirational = /* @__PURE__ */ new Set();
|
|
74063
74274
|
const wasted = /* @__PURE__ */ new Set();
|
|
@@ -74092,7 +74303,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
74092
74303
|
);
|
|
74093
74304
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
74094
74305
|
const project = resolveProject(app.db, request.params.name);
|
|
74095
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
74306
|
+
const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
|
|
74096
74307
|
if (!session || session.projectId !== project.id) {
|
|
74097
74308
|
throw notFound("Discovery session", request.params.id);
|
|
74098
74309
|
}
|
|
@@ -74115,7 +74326,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
74115
74326
|
const bucketSet = new Set(buckets);
|
|
74116
74327
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
74117
74328
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
74118
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
74329
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
|
|
74119
74330
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
74120
74331
|
for (const probe of probeRows) {
|
|
74121
74332
|
if (!probe.bucket) continue;
|
|
@@ -74123,7 +74334,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
74123
74334
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
74124
74335
|
}
|
|
74125
74336
|
const existingQueries = new Set(
|
|
74126
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
74337
|
+
app.db.select({ query: queries.query }).from(queries).where(eq108(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
74127
74338
|
);
|
|
74128
74339
|
const promotedQueries = [];
|
|
74129
74340
|
const skippedQueries = [];
|
|
@@ -74337,7 +74548,7 @@ function planCompetitorPromotion(input) {
|
|
|
74337
74548
|
|
|
74338
74549
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
74339
74550
|
import crypto58 from "crypto";
|
|
74340
|
-
import { eq as
|
|
74551
|
+
import { eq as eq109 } from "drizzle-orm";
|
|
74341
74552
|
var DEFAULT_MAX_PROBES = 100;
|
|
74342
74553
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
74343
74554
|
function classifyProbeBucket(input) {
|
|
@@ -74423,7 +74634,7 @@ async function executeDiscovery(opts) {
|
|
|
74423
74634
|
status: DiscoverySessionStatuses.seeding,
|
|
74424
74635
|
dedupThreshold,
|
|
74425
74636
|
startedAt
|
|
74426
|
-
}).where(
|
|
74637
|
+
}).where(eq109(discoverySessions.id, opts.sessionId)).run();
|
|
74427
74638
|
const seedResult = await opts.deps.seed({
|
|
74428
74639
|
project: opts.project,
|
|
74429
74640
|
icpDescription: opts.icpDescription,
|
|
@@ -74484,7 +74695,7 @@ async function executeDiscovery(opts) {
|
|
|
74484
74695
|
dedupBandPairFraction: dedupStats.bandPairFraction,
|
|
74485
74696
|
dedupPairsTotal: dedupStats.pairsTotal,
|
|
74486
74697
|
warning
|
|
74487
|
-
}).where(
|
|
74698
|
+
}).where(eq109(discoverySessions.id, opts.sessionId)).run();
|
|
74488
74699
|
const probeLocation = opts.locations?.[0];
|
|
74489
74700
|
const probeResults = await mapWithConcurrency(
|
|
74490
74701
|
probedCanonicals,
|
|
@@ -74538,7 +74749,7 @@ async function executeDiscovery(opts) {
|
|
|
74538
74749
|
wastedCount: buckets["wasted-surface"],
|
|
74539
74750
|
competitorMap,
|
|
74540
74751
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
74541
|
-
}).where(
|
|
74752
|
+
}).where(eq109(discoverySessions.id, opts.sessionId)).run();
|
|
74542
74753
|
upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
|
|
74543
74754
|
return {
|
|
74544
74755
|
buckets,
|
|
@@ -74578,7 +74789,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
74578
74789
|
status: DiscoverySessionStatuses.failed,
|
|
74579
74790
|
error,
|
|
74580
74791
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
74581
|
-
}).where(
|
|
74792
|
+
}).where(eq109(discoverySessions.id, sessionId)).run();
|
|
74582
74793
|
}
|
|
74583
74794
|
function dedupeStrings(input) {
|
|
74584
74795
|
const seen2 = /* @__PURE__ */ new Set();
|
|
@@ -74596,12 +74807,12 @@ function dedupeStrings(input) {
|
|
|
74596
74807
|
|
|
74597
74808
|
// ../api-routes/src/technical-aeo.ts
|
|
74598
74809
|
import crypto59 from "crypto";
|
|
74599
|
-
import { and as
|
|
74810
|
+
import { and as and94, asc as asc27, count as count8, desc as desc50, eq as eq111, gte as gte32, inArray as inArray50, isNotNull as isNotNull10, isNull as isNull17, lt as lt19, or as or21, sql as sql45 } from "drizzle-orm";
|
|
74600
74811
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
74601
74812
|
|
|
74602
74813
|
// ../api-routes/src/technical-aeo-issues.ts
|
|
74603
74814
|
import { createHash as createHash10 } from "crypto";
|
|
74604
|
-
import { and as
|
|
74815
|
+
import { and as and93, asc as asc26, eq as eq110, gt as gt10, sql as sql44 } from "drizzle-orm";
|
|
74605
74816
|
var ADVISORY_CODES = /* @__PURE__ */ new Set([
|
|
74606
74817
|
"entity-consistency.title.short",
|
|
74607
74818
|
"site.duplicate-title",
|
|
@@ -74746,10 +74957,10 @@ function readSiteAuditIssues(input) {
|
|
|
74746
74957
|
}
|
|
74747
74958
|
}
|
|
74748
74959
|
if (snapshot.detailsAvailable && snapshot.attemptId) {
|
|
74749
|
-
const scope =
|
|
74750
|
-
|
|
74751
|
-
|
|
74752
|
-
|
|
74960
|
+
const scope = and93(
|
|
74961
|
+
eq110(siteCrawlPages.projectId, project.id),
|
|
74962
|
+
eq110(siteCrawlPages.runId, snapshot.runId),
|
|
74963
|
+
eq110(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
74753
74964
|
);
|
|
74754
74965
|
const size = db.select({ bytes: sql44`coalesce(sum(length(cast(${siteCrawlPages.auditFields} as blob)) + length(cast(${siteCrawlPages.url} as blob))), 0)` }).from(siteCrawlPages).where(scope).get()?.bytes ?? 0;
|
|
74755
74966
|
if (size + crossBytes > MAX_EVIDENCE_BYTES) {
|
|
@@ -74762,7 +74973,7 @@ function readSiteAuditIssues(input) {
|
|
|
74762
74973
|
url: siteCrawlPages.url,
|
|
74763
74974
|
auditScore: siteCrawlPages.auditScore,
|
|
74764
74975
|
auditFields: siteCrawlPages.auditFields
|
|
74765
|
-
}).from(siteCrawlPages).where(
|
|
74976
|
+
}).from(siteCrawlPages).where(and93(scope, after === void 0 ? void 0 : gt10(siteCrawlPages.nodeKey, after))).orderBy(asc26(siteCrawlPages.nodeKey)).limit(200).all();
|
|
74766
74977
|
for (const page2 of pages) {
|
|
74767
74978
|
evidenceHash.update(JSON.stringify(page2));
|
|
74768
74979
|
const fields = record(page2.auditFields);
|
|
@@ -74899,9 +75110,9 @@ var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
|
|
|
74899
75110
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
74900
75111
|
function surfaceableSiteAuditFilters(projectId) {
|
|
74901
75112
|
return [
|
|
74902
|
-
|
|
74903
|
-
|
|
74904
|
-
|
|
75113
|
+
eq111(siteAuditSnapshots.projectId, projectId),
|
|
75114
|
+
eq111(runs.projectId, projectId),
|
|
75115
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
74905
75116
|
inArray50(runs.status, SURFACEABLE_STATUSES),
|
|
74906
75117
|
notProbeRun()
|
|
74907
75118
|
];
|
|
@@ -75142,8 +75353,8 @@ function parseLinkKind(value) {
|
|
|
75142
75353
|
return allowed.data;
|
|
75143
75354
|
}
|
|
75144
75355
|
function linkKindFilter(linkKind) {
|
|
75145
|
-
if (linkKind === SiteHealthLinkKinds.content) return
|
|
75146
|
-
if (linkKind === SiteHealthLinkKinds.template) return
|
|
75356
|
+
if (linkKind === SiteHealthLinkKinds.content) return eq111(siteCrawlEdges.isTemplate, false);
|
|
75357
|
+
if (linkKind === SiteHealthLinkKinds.template) return eq111(siteCrawlEdges.isTemplate, true);
|
|
75147
75358
|
return void 0;
|
|
75148
75359
|
}
|
|
75149
75360
|
var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
|
|
@@ -75247,9 +75458,9 @@ function changedFields(before, after, fields) {
|
|
|
75247
75458
|
}
|
|
75248
75459
|
async function technicalAeoRoutes(app, opts) {
|
|
75249
75460
|
const visibleCrawlFilters = (projectId) => [
|
|
75250
|
-
|
|
75251
|
-
|
|
75252
|
-
|
|
75461
|
+
eq111(siteCrawlSnapshots.projectId, projectId),
|
|
75462
|
+
eq111(runs.projectId, projectId),
|
|
75463
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
75253
75464
|
inArray50(runs.status, SURFACEABLE_STATUSES),
|
|
75254
75465
|
notProbeRun()
|
|
75255
75466
|
];
|
|
@@ -75257,10 +75468,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75257
75468
|
if (!requestedDate) return void 0;
|
|
75258
75469
|
const date = sql45`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
|
|
75259
75470
|
const filters = visibleCrawlFilters(projectId);
|
|
75260
|
-
const allDates =
|
|
75261
|
-
const matchingDates =
|
|
75262
|
-
const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
75263
|
-
const countDates = (where) => app.db.select({ value: sql45`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
75471
|
+
const allDates = and94(...filters);
|
|
75472
|
+
const matchingDates = and94(...filters, eq111(sql45`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
|
|
75473
|
+
const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc50(date)).limit(10).all().map((row) => row.date);
|
|
75474
|
+
const countDates = (where) => app.db.select({ value: sql45`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
|
|
75264
75475
|
return {
|
|
75265
75476
|
recentDates: readDates(allDates),
|
|
75266
75477
|
totalDates: countDates(allDates),
|
|
@@ -75270,15 +75481,15 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75270
75481
|
};
|
|
75271
75482
|
const resolveCrawl = (projectId, runId, requestedDate, scope = {}) => {
|
|
75272
75483
|
const filters = visibleCrawlFilters(projectId);
|
|
75273
|
-
if (scope.recordedBefore) filters.push(
|
|
75484
|
+
if (scope.recordedBefore) filters.push(lt19(siteCrawlSnapshots.createdAt, scope.recordedBefore));
|
|
75274
75485
|
const newestIfComplete = sql45`case when ${siteCrawlSnapshots.complete} then ${siteCrawlSnapshots.createdAt} end`;
|
|
75275
75486
|
if (requestedDate) {
|
|
75276
75487
|
const nextDate2 = /* @__PURE__ */ new Date(`${requestedDate}T00:00:00.000Z`);
|
|
75277
75488
|
nextDate2.setUTCDate(nextDate2.getUTCDate() + 1);
|
|
75278
|
-
filters.push(gte32(siteCrawlSnapshots.createdAt, requestedDate),
|
|
75489
|
+
filters.push(gte32(siteCrawlSnapshots.createdAt, requestedDate), lt19(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
|
|
75279
75490
|
}
|
|
75280
75491
|
const selectionFilters = scope.scoredOnly ? [...filters, sql45`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
|
|
75281
|
-
const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
75492
|
+
const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(...selectionFilters, runId ? eq111(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
|
|
75282
75493
|
desc50(sql45`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
|
|
75283
75494
|
desc50(siteCrawlSnapshots.complete),
|
|
75284
75495
|
desc50(newestIfComplete),
|
|
@@ -75290,18 +75501,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75290
75501
|
const date = selected.snapshot.createdAt.slice(0, 10);
|
|
75291
75502
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
75292
75503
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
75293
|
-
const sameDate =
|
|
75504
|
+
const sameDate = and94(
|
|
75294
75505
|
...filters,
|
|
75295
75506
|
gte32(siteCrawlSnapshots.createdAt, date),
|
|
75296
|
-
|
|
75507
|
+
lt19(siteCrawlSnapshots.createdAt, nextDate.toISOString())
|
|
75297
75508
|
);
|
|
75298
|
-
const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
75509
|
+
const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
75299
75510
|
const candidates = app.db.select({
|
|
75300
75511
|
runId: siteCrawlSnapshots.runId,
|
|
75301
75512
|
createdAt: siteCrawlSnapshots.createdAt,
|
|
75302
75513
|
complete: siteCrawlSnapshots.complete,
|
|
75303
75514
|
pages: siteCrawlSnapshots.pagesFetched
|
|
75304
|
-
}).from(siteCrawlSnapshots).innerJoin(runs,
|
|
75515
|
+
}).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
75305
75516
|
desc50(siteCrawlSnapshots.complete),
|
|
75306
75517
|
desc50(newestIfComplete),
|
|
75307
75518
|
desc50(siteCrawlSnapshots.pagesFetched),
|
|
@@ -75321,11 +75532,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75321
75532
|
const resolveAudit = (projectId, runId, recordedBefore) => {
|
|
75322
75533
|
const filters = [
|
|
75323
75534
|
...surfaceableSiteAuditFilters(projectId),
|
|
75324
|
-
...recordedBefore ? [
|
|
75535
|
+
...recordedBefore ? [lt19(siteAuditSnapshots.createdAt, recordedBefore)] : []
|
|
75325
75536
|
];
|
|
75326
75537
|
const complete = sql45`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
|
|
75327
75538
|
const newestIfComplete = sql45`case when ${runs.status} = ${RunStatuses.completed} then ${siteAuditSnapshots.createdAt} end`;
|
|
75328
|
-
const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs,
|
|
75539
|
+
const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(...filters, pinnedRunId ? eq111(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
|
|
75329
75540
|
desc50(sql45`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
|
|
75330
75541
|
desc50(complete),
|
|
75331
75542
|
desc50(newestIfComplete),
|
|
@@ -75347,18 +75558,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75347
75558
|
const date = selected.snap.createdAt.slice(0, 10);
|
|
75348
75559
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
75349
75560
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
75350
|
-
const sameDate =
|
|
75561
|
+
const sameDate = and94(
|
|
75351
75562
|
...filters,
|
|
75352
75563
|
gte32(siteAuditSnapshots.createdAt, date),
|
|
75353
|
-
|
|
75564
|
+
lt19(siteAuditSnapshots.createdAt, nextDate.toISOString())
|
|
75354
75565
|
);
|
|
75355
|
-
const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs,
|
|
75566
|
+
const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
75356
75567
|
const candidates = app.db.select({
|
|
75357
75568
|
runId: siteAuditSnapshots.runId,
|
|
75358
75569
|
createdAt: siteAuditSnapshots.createdAt,
|
|
75359
75570
|
complete,
|
|
75360
75571
|
pages: siteAuditSnapshots.pagesAudited
|
|
75361
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
75572
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
75362
75573
|
desc50(complete),
|
|
75363
75574
|
desc50(newestIfComplete),
|
|
75364
75575
|
desc50(siteAuditSnapshots.pagesAudited),
|
|
@@ -75425,19 +75636,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75425
75636
|
};
|
|
75426
75637
|
const inventorySummaryFor = (scope) => {
|
|
75427
75638
|
if (!scope) return null;
|
|
75428
|
-
const inScope =
|
|
75429
|
-
|
|
75430
|
-
|
|
75431
|
-
|
|
75639
|
+
const inScope = and94(
|
|
75640
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
75641
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
75642
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId)
|
|
75432
75643
|
);
|
|
75433
75644
|
const groups = crawlReasonGroups(inScope);
|
|
75434
75645
|
const total = groups.reduce((sum, group) => sum + group.pages, 0);
|
|
75435
75646
|
const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
|
|
75436
|
-
const urlVersions = app.db.select({ value: count8() }).from(siteCrawlPages).where(
|
|
75437
|
-
const urlVersionsNotFetched = app.db.select({ value: siteCrawlAttempts.urlVersionsSkipped }).from(siteCrawlAttempts).where(
|
|
75438
|
-
|
|
75439
|
-
|
|
75440
|
-
|
|
75647
|
+
const urlVersions = app.db.select({ value: count8() }).from(siteCrawlPages).where(and94(inScope, isNotNull10(siteCrawlPages.urlVersionOf))).get()?.value ?? 0;
|
|
75648
|
+
const urlVersionsNotFetched = app.db.select({ value: siteCrawlAttempts.urlVersionsSkipped }).from(siteCrawlAttempts).where(and94(
|
|
75649
|
+
eq111(siteCrawlAttempts.projectId, scope.projectId),
|
|
75650
|
+
eq111(siteCrawlAttempts.runId, scope.runId),
|
|
75651
|
+
eq111(siteCrawlAttempts.id, scope.attemptId)
|
|
75441
75652
|
)).get()?.value ?? 0;
|
|
75442
75653
|
return {
|
|
75443
75654
|
scope: "selected-snapshot",
|
|
@@ -75457,33 +75668,33 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75457
75668
|
const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
|
|
75458
75669
|
const pageInScope = (scope, selector) => {
|
|
75459
75670
|
if (!selector.nodeKey && !selector.url) return null;
|
|
75460
|
-
return app.db.select().from(siteCrawlPages).where(
|
|
75461
|
-
|
|
75462
|
-
|
|
75463
|
-
|
|
75464
|
-
selector.nodeKey ?
|
|
75671
|
+
return app.db.select().from(siteCrawlPages).where(and94(
|
|
75672
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
75673
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
75674
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
75675
|
+
selector.nodeKey ? eq111(siteCrawlPages.nodeKey, selector.nodeKey) : eq111(siteCrawlPages.url, selector.url)
|
|
75465
75676
|
)).orderBy(asc27(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
75466
75677
|
};
|
|
75467
|
-
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(
|
|
75468
|
-
|
|
75469
|
-
|
|
75470
|
-
|
|
75471
|
-
|
|
75678
|
+
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and94(
|
|
75679
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
75680
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
75681
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
75682
|
+
eq111(siteCrawlPages.depth, 0)
|
|
75472
75683
|
)).orderBy(asc27(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
75473
|
-
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(
|
|
75474
|
-
|
|
75475
|
-
|
|
75476
|
-
|
|
75684
|
+
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and94(
|
|
75685
|
+
eq111(runs.id, runId),
|
|
75686
|
+
eq111(runs.projectId, projectId),
|
|
75687
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
75477
75688
|
inArray50(runs.status, SURFACEABLE_STATUSES),
|
|
75478
75689
|
notProbeRun()
|
|
75479
75690
|
)).limit(1).get());
|
|
75480
75691
|
const assertKnownAuditRun = (projectId, runId) => {
|
|
75481
75692
|
if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
|
|
75482
75693
|
};
|
|
75483
|
-
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs,
|
|
75484
|
-
|
|
75485
|
-
|
|
75486
|
-
|
|
75694
|
+
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(
|
|
75695
|
+
eq111(siteAuditSnapshots.projectId, projectId),
|
|
75696
|
+
eq111(runs.projectId, projectId),
|
|
75697
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
75487
75698
|
inArray50(runs.status, SURFACEABLE_STATUSES),
|
|
75488
75699
|
notProbeRun()
|
|
75489
75700
|
)).limit(1).get());
|
|
@@ -75585,9 +75796,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75585
75796
|
};
|
|
75586
75797
|
}
|
|
75587
75798
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
75588
|
-
const conds = [
|
|
75589
|
-
if (statusFilter) conds.push(
|
|
75590
|
-
const where =
|
|
75799
|
+
const conds = [eq111(siteAuditPages.projectId, project.id), eq111(siteAuditPages.runId, latest.snap.runId)];
|
|
75800
|
+
if (statusFilter) conds.push(eq111(siteAuditPages.status, statusFilter));
|
|
75801
|
+
const where = and94(...conds);
|
|
75591
75802
|
const totalRow = app.db.select({ value: count8() }).from(siteAuditPages).where(where).get();
|
|
75592
75803
|
const total = totalRow?.value ?? 0;
|
|
75593
75804
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
@@ -75611,7 +75822,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75611
75822
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
75612
75823
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
75613
75824
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
75614
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
75825
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(...surfaceableSiteAuditFilters(project.id))).orderBy(desc50(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
75615
75826
|
return { project: project.name, points: rows.reverse() };
|
|
75616
75827
|
});
|
|
75617
75828
|
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
@@ -75711,10 +75922,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75711
75922
|
{ projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
|
|
75712
75923
|
snapshot.rootUrl
|
|
75713
75924
|
)?.nodeKey ?? null;
|
|
75714
|
-
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(
|
|
75715
|
-
|
|
75716
|
-
|
|
75717
|
-
|
|
75925
|
+
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and94(
|
|
75926
|
+
eq111(siteCrawlGraphLayouts.projectId, project.id),
|
|
75927
|
+
eq111(siteCrawlGraphLayouts.runId, snapshot.runId),
|
|
75928
|
+
eq111(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
|
|
75718
75929
|
)).limit(1).get();
|
|
75719
75930
|
if (!persistedLayout) {
|
|
75720
75931
|
return {
|
|
@@ -75767,9 +75978,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75767
75978
|
const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
|
|
75768
75979
|
const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
|
|
75769
75980
|
const graphScope = [
|
|
75770
|
-
|
|
75771
|
-
|
|
75772
|
-
|
|
75981
|
+
eq111(siteCrawlGraphNodes.projectId, project.id),
|
|
75982
|
+
eq111(siteCrawlGraphNodes.runId, snapshot.runId),
|
|
75983
|
+
eq111(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
|
|
75773
75984
|
];
|
|
75774
75985
|
const nodeRows = app.db.select({
|
|
75775
75986
|
nodeKey: siteCrawlPages.nodeKey,
|
|
@@ -75788,18 +75999,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75788
75999
|
linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
|
|
75789
76000
|
x: siteCrawlGraphNodes.x,
|
|
75790
76001
|
y: siteCrawlGraphNodes.y
|
|
75791
|
-
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages,
|
|
75792
|
-
|
|
75793
|
-
|
|
75794
|
-
|
|
75795
|
-
|
|
75796
|
-
)).where(
|
|
76002
|
+
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and94(
|
|
76003
|
+
eq111(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
|
|
76004
|
+
eq111(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
|
|
76005
|
+
eq111(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
|
|
76006
|
+
eq111(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
|
|
76007
|
+
)).where(and94(...graphScope, lt19(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc27(siteCrawlGraphNodes.sampleRank)).all();
|
|
75797
76008
|
const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
|
|
75798
76009
|
...row,
|
|
75799
76010
|
healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
|
|
75800
76011
|
healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
|
|
75801
76012
|
}));
|
|
75802
|
-
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 :
|
|
76013
|
+
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq111(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
|
|
75803
76014
|
const edges = app.db.select({
|
|
75804
76015
|
edgeKey: siteCrawlGraphEdges.edgeKey,
|
|
75805
76016
|
sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
|
|
@@ -75807,23 +76018,23 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75807
76018
|
followable: siteCrawlGraphEdges.followable,
|
|
75808
76019
|
occurrences: siteCrawlGraphEdges.occurrences,
|
|
75809
76020
|
isTemplate: siteCrawlGraphEdges.isTemplate
|
|
75810
|
-
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode,
|
|
75811
|
-
|
|
75812
|
-
|
|
75813
|
-
|
|
75814
|
-
|
|
75815
|
-
|
|
75816
|
-
)).innerJoin(graphTargetNode,
|
|
75817
|
-
|
|
75818
|
-
|
|
75819
|
-
|
|
75820
|
-
|
|
75821
|
-
|
|
75822
|
-
)).where(
|
|
75823
|
-
|
|
75824
|
-
|
|
75825
|
-
|
|
75826
|
-
|
|
76021
|
+
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and94(
|
|
76022
|
+
eq111(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
|
|
76023
|
+
eq111(graphSourceNode.runId, siteCrawlGraphEdges.runId),
|
|
76024
|
+
eq111(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
76025
|
+
eq111(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
|
|
76026
|
+
lt19(graphSourceNode.sampleRank, maxNodes)
|
|
76027
|
+
)).innerJoin(graphTargetNode, and94(
|
|
76028
|
+
eq111(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
|
|
76029
|
+
eq111(graphTargetNode.runId, siteCrawlGraphEdges.runId),
|
|
76030
|
+
eq111(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
76031
|
+
eq111(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
|
|
76032
|
+
lt19(graphTargetNode.sampleRank, maxNodes)
|
|
76033
|
+
)).where(and94(
|
|
76034
|
+
eq111(siteCrawlGraphEdges.projectId, project.id),
|
|
76035
|
+
eq111(siteCrawlGraphEdges.runId, snapshot.runId),
|
|
76036
|
+
eq111(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
|
|
76037
|
+
lt19(siteCrawlGraphEdges.sampleRank, maxEdges),
|
|
75827
76038
|
graphLinkKindFilter
|
|
75828
76039
|
)).orderBy(asc27(siteCrawlGraphEdges.sampleRank)).all();
|
|
75829
76040
|
const totalNodes = persistedLayout.totalNodes;
|
|
@@ -75917,11 +76128,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75917
76128
|
let frontier = [focus.nodeKey];
|
|
75918
76129
|
let queryTruncated = false;
|
|
75919
76130
|
for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
|
|
75920
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
75921
|
-
|
|
75922
|
-
|
|
75923
|
-
|
|
75924
|
-
|
|
76131
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and94(
|
|
76132
|
+
eq111(siteCrawlEdges.projectId, scope.projectId),
|
|
76133
|
+
eq111(siteCrawlEdges.runId, scope.runId),
|
|
76134
|
+
eq111(siteCrawlEdges.attemptId, scope.attemptId),
|
|
76135
|
+
eq111(siteCrawlEdges.internal, true),
|
|
75925
76136
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
75926
76137
|
or21(
|
|
75927
76138
|
inArray50(siteCrawlEdges.sourceNodeKey, frontier),
|
|
@@ -75958,19 +76169,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75958
76169
|
frontier = [...next].sort();
|
|
75959
76170
|
}
|
|
75960
76171
|
const nodeKeys = [...distances.keys()];
|
|
75961
|
-
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
75962
|
-
|
|
75963
|
-
|
|
75964
|
-
|
|
76172
|
+
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and94(
|
|
76173
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
76174
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
76175
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
75965
76176
|
inArray50(siteCrawlPages.nodeKey, nodeKeys)
|
|
75966
76177
|
)).all();
|
|
75967
76178
|
const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
|
|
75968
76179
|
const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
|
|
75969
|
-
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(
|
|
75970
|
-
|
|
75971
|
-
|
|
75972
|
-
|
|
75973
|
-
|
|
76180
|
+
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and94(
|
|
76181
|
+
eq111(siteCrawlEdges.projectId, scope.projectId),
|
|
76182
|
+
eq111(siteCrawlEdges.runId, scope.runId),
|
|
76183
|
+
eq111(siteCrawlEdges.attemptId, scope.attemptId),
|
|
76184
|
+
eq111(siteCrawlEdges.internal, true),
|
|
75974
76185
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
75975
76186
|
inArray50(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
|
|
75976
76187
|
inArray50(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
|
|
@@ -76066,22 +76277,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76066
76277
|
let found = fromPage.nodeKey === toPage.nodeKey;
|
|
76067
76278
|
let truncated = false;
|
|
76068
76279
|
for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
|
|
76069
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
76070
|
-
|
|
76071
|
-
|
|
76072
|
-
|
|
76073
|
-
|
|
76074
|
-
|
|
76075
|
-
|
|
76280
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and94(
|
|
76281
|
+
eq111(siteCrawlEdges.projectId, scope.projectId),
|
|
76282
|
+
eq111(siteCrawlEdges.runId, scope.runId),
|
|
76283
|
+
eq111(siteCrawlEdges.attemptId, scope.attemptId),
|
|
76284
|
+
eq111(siteCrawlEdges.internal, true),
|
|
76285
|
+
eq111(siteCrawlEdges.followable, true),
|
|
76286
|
+
eq111(siteCrawlEdges.relation, "anchor"),
|
|
76076
76287
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
76077
76288
|
inArray50(siteCrawlEdges.sourceNodeKey, frontier)
|
|
76078
76289
|
)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
|
|
76079
76290
|
if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
|
|
76080
76291
|
const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
|
|
76081
|
-
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(
|
|
76082
|
-
|
|
76083
|
-
|
|
76084
|
-
|
|
76292
|
+
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and94(
|
|
76293
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
76294
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
76295
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
76085
76296
|
inArray50(siteCrawlPages.nodeKey, candidateTargetKeys)
|
|
76086
76297
|
)).all().map((row) => row.nodeKey));
|
|
76087
76298
|
const next = /* @__PURE__ */ new Set();
|
|
@@ -76123,10 +76334,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76123
76334
|
pathEdges.unshift(step.edge);
|
|
76124
76335
|
pathKeys.unshift(step.nodeKey);
|
|
76125
76336
|
}
|
|
76126
|
-
const pathRows = app.db.select().from(siteCrawlPages).where(
|
|
76127
|
-
|
|
76128
|
-
|
|
76129
|
-
|
|
76337
|
+
const pathRows = app.db.select().from(siteCrawlPages).where(and94(
|
|
76338
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
76339
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
76340
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
76130
76341
|
inArray50(siteCrawlPages.nodeKey, pathKeys)
|
|
76131
76342
|
)).all();
|
|
76132
76343
|
const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
|
|
@@ -76155,22 +76366,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76155
76366
|
throw validationError("change must be all, added, removed, or changed");
|
|
76156
76367
|
}
|
|
76157
76368
|
const completeSnapshotFilters = [
|
|
76158
|
-
|
|
76159
|
-
|
|
76160
|
-
|
|
76161
|
-
|
|
76162
|
-
|
|
76369
|
+
eq111(siteCrawlSnapshots.projectId, project.id),
|
|
76370
|
+
eq111(runs.projectId, project.id),
|
|
76371
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
76372
|
+
eq111(runs.status, RunStatuses.completed),
|
|
76373
|
+
eq111(siteCrawlSnapshots.complete, true),
|
|
76163
76374
|
notProbeRun()
|
|
76164
76375
|
];
|
|
76165
|
-
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
76376
|
+
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(...completeSnapshotFilters)).orderBy(desc50(siteCrawlSnapshots.createdAt), desc50(siteCrawlSnapshots.runId)).limit(1).get();
|
|
76166
76377
|
if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
|
|
76167
|
-
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
76378
|
+
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(
|
|
76168
76379
|
...completeSnapshotFilters,
|
|
76169
76380
|
or21(
|
|
76170
|
-
|
|
76171
|
-
|
|
76172
|
-
|
|
76173
|
-
|
|
76381
|
+
lt19(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
76382
|
+
and94(
|
|
76383
|
+
eq111(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
76384
|
+
lt19(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
|
|
76174
76385
|
)
|
|
76175
76386
|
)
|
|
76176
76387
|
)).orderBy(desc50(siteCrawlSnapshots.createdAt), desc50(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
|
|
@@ -76414,17 +76625,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76414
76625
|
const visibleKeys = keyRows.slice(0, limit);
|
|
76415
76626
|
const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
|
|
76416
76627
|
const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
|
|
76417
|
-
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
76418
|
-
|
|
76419
|
-
|
|
76420
|
-
|
|
76628
|
+
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and94(
|
|
76629
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
76630
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
76631
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
76421
76632
|
inArray50(siteCrawlPages.nodeKey, pageKeys)
|
|
76422
76633
|
)).all();
|
|
76423
|
-
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(
|
|
76424
|
-
|
|
76425
|
-
|
|
76426
|
-
|
|
76427
|
-
|
|
76634
|
+
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and94(
|
|
76635
|
+
eq111(siteCrawlEdges.projectId, scope.projectId),
|
|
76636
|
+
eq111(siteCrawlEdges.runId, scope.runId),
|
|
76637
|
+
eq111(siteCrawlEdges.attemptId, scope.attemptId),
|
|
76638
|
+
eq111(siteCrawlEdges.internal, true),
|
|
76428
76639
|
inArray50(siteCrawlEdges.edgeKey, linkKeys)
|
|
76429
76640
|
)).all();
|
|
76430
76641
|
const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
@@ -76598,16 +76809,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76598
76809
|
}
|
|
76599
76810
|
const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
|
|
76600
76811
|
const filters = [
|
|
76601
|
-
|
|
76602
|
-
|
|
76603
|
-
|
|
76812
|
+
eq111(siteCrawlPages.projectId, project.id),
|
|
76813
|
+
eq111(siteCrawlPages.runId, snapshot.runId),
|
|
76814
|
+
eq111(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
76604
76815
|
];
|
|
76605
|
-
if (request.query.nodeKey) filters.push(
|
|
76816
|
+
if (request.query.nodeKey) filters.push(eq111(siteCrawlPages.nodeKey, request.query.nodeKey));
|
|
76606
76817
|
const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
|
|
76607
|
-
if (inventoryEligible != null) filters.push(
|
|
76608
|
-
if (request.query.fetchState) filters.push(
|
|
76609
|
-
if (request.query.indexabilityState) filters.push(
|
|
76610
|
-
if (request.query.auditState) filters.push(
|
|
76818
|
+
if (inventoryEligible != null) filters.push(eq111(siteCrawlPages.inventoryEligible, inventoryEligible));
|
|
76819
|
+
if (request.query.fetchState) filters.push(eq111(siteCrawlPages.fetchState, request.query.fetchState));
|
|
76820
|
+
if (request.query.indexabilityState) filters.push(eq111(siteCrawlPages.indexabilityState, request.query.indexabilityState));
|
|
76821
|
+
if (request.query.auditState) filters.push(eq111(siteCrawlPages.auditState, request.query.auditState));
|
|
76611
76822
|
const urlVersions = parseUrlVersionFilter(request.query.urlVersions);
|
|
76612
76823
|
if (urlVersions === SiteCrawlUrlVersionFilters.exclude) filters.push(isNull17(siteCrawlPages.urlVersionOf));
|
|
76613
76824
|
if (urlVersions === SiteCrawlUrlVersionFilters.only) filters.push(isNotNull10(siteCrawlPages.urlVersionOf));
|
|
@@ -76633,14 +76844,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76633
76844
|
const orderBy = sort === "score-desc" ? [desc50(siteCrawlPages.auditScore), asc27(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc27(siteCrawlPages.auditScore), asc27(siteCrawlPages.nodeKey)] : sort === "path" ? [asc27(siteCrawlPages.path), asc27(siteCrawlPages.nodeKey)] : [asc27(siteCrawlPages.url), asc27(siteCrawlPages.nodeKey)];
|
|
76634
76845
|
let healthStateFilter = null;
|
|
76635
76846
|
if (healthState) {
|
|
76636
|
-
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(
|
|
76637
|
-
|
|
76638
|
-
|
|
76639
|
-
|
|
76847
|
+
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and94(
|
|
76848
|
+
eq111(siteCrawlPages.projectId, project.id),
|
|
76849
|
+
eq111(siteCrawlPages.runId, snapshot.runId),
|
|
76850
|
+
eq111(siteCrawlPages.attemptId, snapshot.attemptId),
|
|
76640
76851
|
isNull17(siteCrawlPages.healthState)
|
|
76641
76852
|
)).limit(1).get();
|
|
76642
76853
|
healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
|
|
76643
|
-
if (healthStateFilter === "applied") filters.push(
|
|
76854
|
+
if (healthStateFilter === "applied") filters.push(eq111(siteCrawlPages.healthState, healthState));
|
|
76644
76855
|
}
|
|
76645
76856
|
if (healthStateFilter === "unavailable-legacy-scan") {
|
|
76646
76857
|
return {
|
|
@@ -76657,7 +76868,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76657
76868
|
pages: []
|
|
76658
76869
|
};
|
|
76659
76870
|
}
|
|
76660
|
-
const where =
|
|
76871
|
+
const where = and94(...filters);
|
|
76661
76872
|
const total = app.db.select({ value: count8() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
|
|
76662
76873
|
const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
|
|
76663
76874
|
const nextOffset = offset + rows.length;
|
|
@@ -76692,10 +76903,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76692
76903
|
url: siteCrawlPages.url,
|
|
76693
76904
|
inventoryEligible: siteCrawlPages.inventoryEligible,
|
|
76694
76905
|
fetchState: siteCrawlPages.fetchState
|
|
76695
|
-
}).from(siteCrawlPages).where(
|
|
76696
|
-
|
|
76697
|
-
|
|
76698
|
-
|
|
76906
|
+
}).from(siteCrawlPages).where(and94(
|
|
76907
|
+
eq111(siteCrawlPages.projectId, project.id),
|
|
76908
|
+
eq111(siteCrawlPages.runId, snapshot.runId),
|
|
76909
|
+
eq111(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
76699
76910
|
)).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
|
|
76700
76911
|
if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
|
|
76701
76912
|
throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
|
|
@@ -76777,17 +76988,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76777
76988
|
};
|
|
76778
76989
|
}
|
|
76779
76990
|
const filters = [
|
|
76780
|
-
|
|
76781
|
-
|
|
76782
|
-
|
|
76783
|
-
|
|
76991
|
+
eq111(siteCrawlEdges.projectId, project.id),
|
|
76992
|
+
eq111(siteCrawlEdges.runId, snapshot.runId),
|
|
76993
|
+
eq111(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
76994
|
+
eq111(siteCrawlEdges.internal, true),
|
|
76784
76995
|
linkKindFilter(linkKind)
|
|
76785
76996
|
];
|
|
76786
|
-
if (request.query.sourceUrl) filters.push(
|
|
76787
|
-
if (request.query.targetUrl) filters.push(
|
|
76997
|
+
if (request.query.sourceUrl) filters.push(eq111(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
|
|
76998
|
+
if (request.query.targetUrl) filters.push(eq111(siteCrawlEdges.targetUrl, request.query.targetUrl));
|
|
76788
76999
|
const followable = parseBoolean2(request.query.followable);
|
|
76789
|
-
if (followable != null) filters.push(
|
|
76790
|
-
const where =
|
|
77000
|
+
if (followable != null) filters.push(eq111(siteCrawlEdges.followable, followable));
|
|
77001
|
+
const where = and94(...filters);
|
|
76791
77002
|
const total = app.db.select({ value: count8() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
|
|
76792
77003
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
76793
77004
|
const offset = decodeCursor(request.query.cursor);
|
|
@@ -76848,17 +77059,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76848
77059
|
};
|
|
76849
77060
|
}
|
|
76850
77061
|
const scope = [
|
|
76851
|
-
|
|
76852
|
-
|
|
76853
|
-
|
|
76854
|
-
|
|
77062
|
+
eq111(siteCrawlEdges.projectId, project.id),
|
|
77063
|
+
eq111(siteCrawlEdges.runId, snapshot.runId),
|
|
77064
|
+
eq111(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
77065
|
+
eq111(siteCrawlEdges.internal, true),
|
|
76855
77066
|
linkKindFilter(linkKind)
|
|
76856
77067
|
];
|
|
76857
|
-
const inboundMatch = request.query.nodeKey && request.query.url ? or21(
|
|
76858
|
-
const outboundMatch = request.query.nodeKey && request.query.url ? or21(
|
|
77068
|
+
const inboundMatch = request.query.nodeKey && request.query.url ? or21(eq111(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq111(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq111(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq111(siteCrawlEdges.targetUrl, request.query.url);
|
|
77069
|
+
const outboundMatch = request.query.nodeKey && request.query.url ? or21(eq111(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq111(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq111(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq111(siteCrawlEdges.sourceUrl, request.query.url);
|
|
76859
77070
|
const limit = parseBoundedLimit(request.query.limit, 50, 100);
|
|
76860
|
-
const inboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
76861
|
-
const outboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
77071
|
+
const inboundRows = app.db.select().from(siteCrawlEdges).where(and94(...scope, inboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
77072
|
+
const outboundRows = app.db.select().from(siteCrawlEdges).where(and94(...scope, outboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
76862
77073
|
return {
|
|
76863
77074
|
project: project.name,
|
|
76864
77075
|
hasCrawlData: true,
|
|
@@ -76892,11 +77103,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76892
77103
|
if (!snapshot.attemptId) {
|
|
76893
77104
|
return { project: project.name, runId: snapshot.runId, state: "partial", checkDeadLinks: true, checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound, unverified: snapshot.deadLinksUnverified, total: 0, nextCursor: null, deadLinks: [], runSelection: target.runSelection };
|
|
76894
77105
|
}
|
|
76895
|
-
const where =
|
|
76896
|
-
|
|
76897
|
-
|
|
76898
|
-
|
|
76899
|
-
|
|
77106
|
+
const where = and94(
|
|
77107
|
+
eq111(siteCrawlFindings.projectId, project.id),
|
|
77108
|
+
eq111(siteCrawlFindings.runId, snapshot.runId),
|
|
77109
|
+
eq111(siteCrawlFindings.attemptId, snapshot.attemptId),
|
|
77110
|
+
eq111(siteCrawlFindings.findingType, "dead-link"),
|
|
76900
77111
|
// Belt and braces on the client-facing surface: a row without a status
|
|
76901
77112
|
// code is not evidence of a broken link, and this response is where such
|
|
76902
77113
|
// a row would be read as one. Migration 140 removed every stored row that
|
|
@@ -76946,16 +77157,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76946
77157
|
createdAt: runs.createdAt,
|
|
76947
77158
|
startedAt: runs.startedAt,
|
|
76948
77159
|
finishedAt: runs.finishedAt
|
|
76949
|
-
}).from(runs).where(
|
|
76950
|
-
|
|
76951
|
-
|
|
77160
|
+
}).from(runs).where(and94(
|
|
77161
|
+
eq111(runs.projectId, project.id),
|
|
77162
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
76952
77163
|
notProbeRun()
|
|
76953
77164
|
)).orderBy(desc50(runs.createdAt), desc50(runs.id)).limit(limit).all();
|
|
76954
77165
|
const runIds = rows.map((row) => row.runId);
|
|
76955
|
-
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
76956
|
-
|
|
76957
|
-
|
|
76958
|
-
|
|
77166
|
+
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(
|
|
77167
|
+
eq111(siteCrawlSnapshots.projectId, project.id),
|
|
77168
|
+
eq111(runs.projectId, project.id),
|
|
77169
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
76959
77170
|
inArray50(runs.status, SURFACEABLE_STATUSES),
|
|
76960
77171
|
notProbeRun(),
|
|
76961
77172
|
inArray50(siteCrawlSnapshots.runId, runIds)
|
|
@@ -76972,21 +77183,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76972
77183
|
});
|
|
76973
77184
|
app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
|
|
76974
77185
|
const project = resolveProject(app.db, request.params.name);
|
|
76975
|
-
const run = app.db.select().from(runs).where(
|
|
76976
|
-
|
|
76977
|
-
|
|
76978
|
-
|
|
77186
|
+
const run = app.db.select().from(runs).where(and94(
|
|
77187
|
+
eq111(runs.id, request.params.runId),
|
|
77188
|
+
eq111(runs.projectId, project.id),
|
|
77189
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
76979
77190
|
notProbeRun()
|
|
76980
77191
|
)).get();
|
|
76981
77192
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
76982
|
-
const attempt = app.db.select().from(siteCrawlAttempts).where(
|
|
76983
|
-
|
|
76984
|
-
|
|
77193
|
+
const attempt = app.db.select().from(siteCrawlAttempts).where(and94(
|
|
77194
|
+
eq111(siteCrawlAttempts.projectId, project.id),
|
|
77195
|
+
eq111(siteCrawlAttempts.runId, run.id)
|
|
76985
77196
|
)).orderBy(desc50(siteCrawlAttempts.attemptNumber), desc50(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
76986
|
-
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(
|
|
76987
|
-
|
|
76988
|
-
|
|
76989
|
-
|
|
77197
|
+
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and94(
|
|
77198
|
+
eq111(siteCrawlGraphLayouts.projectId, project.id),
|
|
77199
|
+
eq111(siteCrawlGraphLayouts.runId, run.id),
|
|
77200
|
+
eq111(siteCrawlGraphLayouts.attemptId, attempt.id)
|
|
76990
77201
|
)).get() : void 0;
|
|
76991
77202
|
const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
|
|
76992
77203
|
return {
|
|
@@ -77017,18 +77228,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
77017
77228
|
};
|
|
77018
77229
|
});
|
|
77019
77230
|
app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
|
|
77020
|
-
const project = tx.select().from(projects).where(
|
|
77231
|
+
const project = tx.select().from(projects).where(eq111(projects.name, request.params.name)).get();
|
|
77021
77232
|
if (!project) throw notFound("Project", request.params.name);
|
|
77022
|
-
const run = tx.select().from(runs).where(
|
|
77023
|
-
|
|
77024
|
-
|
|
77025
|
-
|
|
77233
|
+
const run = tx.select().from(runs).where(and94(
|
|
77234
|
+
eq111(runs.id, request.params.runId),
|
|
77235
|
+
eq111(runs.projectId, project.id),
|
|
77236
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
77026
77237
|
notProbeRun()
|
|
77027
77238
|
)).get();
|
|
77028
77239
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
77029
|
-
const attempt = tx.select().from(siteCrawlAttempts).where(
|
|
77030
|
-
|
|
77031
|
-
|
|
77240
|
+
const attempt = tx.select().from(siteCrawlAttempts).where(and94(
|
|
77241
|
+
eq111(siteCrawlAttempts.projectId, project.id),
|
|
77242
|
+
eq111(siteCrawlAttempts.runId, run.id)
|
|
77032
77243
|
)).orderBy(desc50(siteCrawlAttempts.attemptNumber), desc50(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
77033
77244
|
const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
|
|
77034
77245
|
const base = {
|
|
@@ -77042,11 +77253,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
77042
77253
|
if (!attempt) {
|
|
77043
77254
|
return { ...base, pagesAudited: 0, examples: [] };
|
|
77044
77255
|
}
|
|
77045
|
-
const auditedWhere =
|
|
77046
|
-
|
|
77047
|
-
|
|
77048
|
-
|
|
77049
|
-
|
|
77256
|
+
const auditedWhere = and94(
|
|
77257
|
+
eq111(siteCrawlPages.projectId, project.id),
|
|
77258
|
+
eq111(siteCrawlPages.runId, run.id),
|
|
77259
|
+
eq111(siteCrawlPages.attemptId, attempt.id),
|
|
77260
|
+
eq111(siteCrawlPages.auditState, "success")
|
|
77050
77261
|
);
|
|
77051
77262
|
const pagesAudited = tx.select({ value: count8() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
|
|
77052
77263
|
if (state !== "collecting") {
|
|
@@ -77057,10 +77268,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
77057
77268
|
url: siteCrawlPages.url,
|
|
77058
77269
|
auditScore: siteCrawlPages.auditScore,
|
|
77059
77270
|
auditFields: siteCrawlPages.auditFields
|
|
77060
|
-
}).from(siteCrawlPages).where(
|
|
77271
|
+
}).from(siteCrawlPages).where(and94(
|
|
77061
77272
|
auditedWhere,
|
|
77062
77273
|
isNotNull10(siteCrawlPages.auditScore),
|
|
77063
|
-
|
|
77274
|
+
lt19(siteCrawlPages.auditScore, 70)
|
|
77064
77275
|
)).orderBy(asc27(siteCrawlPages.auditScore), asc27(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
|
|
77065
77276
|
const examples = candidates.flatMap((candidate) => {
|
|
77066
77277
|
if (candidate.auditScore == null) return [];
|
|
@@ -77093,12 +77304,12 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
77093
77304
|
status: runs.status,
|
|
77094
77305
|
identityKey: siteCrawlRunRequests.identityKey,
|
|
77095
77306
|
effectiveOptions: siteCrawlRunRequests.effectiveOptions
|
|
77096
|
-
}).from(runs).leftJoin(siteCrawlRunRequests,
|
|
77097
|
-
|
|
77098
|
-
|
|
77099
|
-
)).where(
|
|
77100
|
-
|
|
77101
|
-
|
|
77307
|
+
}).from(runs).leftJoin(siteCrawlRunRequests, and94(
|
|
77308
|
+
eq111(siteCrawlRunRequests.projectId, runs.projectId),
|
|
77309
|
+
eq111(siteCrawlRunRequests.runId, runs.id)
|
|
77310
|
+
)).where(and94(
|
|
77311
|
+
eq111(runs.projectId, project.id),
|
|
77312
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
77102
77313
|
inArray50(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
77103
77314
|
)).get();
|
|
77104
77315
|
if (existing) {
|
|
@@ -77153,7 +77364,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
77153
77364
|
}
|
|
77154
77365
|
|
|
77155
77366
|
// ../api-routes/src/trends.ts
|
|
77156
|
-
import { and as
|
|
77367
|
+
import { and as and95, asc as asc28, eq as eq112 } from "drizzle-orm";
|
|
77157
77368
|
function parseSurface(value) {
|
|
77158
77369
|
const parsed = trendSurfaceSchema.safeParse(value);
|
|
77159
77370
|
if (!parsed.success) {
|
|
@@ -77209,8 +77420,8 @@ async function trendsRoutes(app) {
|
|
|
77209
77420
|
function readSurface(app, projectId, surface) {
|
|
77210
77421
|
switch (surface) {
|
|
77211
77422
|
case TrendSurfaces["index-coverage"]: {
|
|
77212
|
-
const google = app.db.select({ date: gscCoverageSnapshots.date, indexed: gscCoverageSnapshots.indexed, notIndexed: gscCoverageSnapshots.notIndexed }).from(gscCoverageSnapshots).where(
|
|
77213
|
-
const bing = app.db.select({ date: bingCoverageSnapshots.date, indexed: bingCoverageSnapshots.indexed, notIndexed: bingCoverageSnapshots.notIndexed }).from(bingCoverageSnapshots).where(
|
|
77423
|
+
const google = app.db.select({ date: gscCoverageSnapshots.date, indexed: gscCoverageSnapshots.indexed, notIndexed: gscCoverageSnapshots.notIndexed }).from(gscCoverageSnapshots).where(eq112(gscCoverageSnapshots.projectId, projectId)).orderBy(asc28(gscCoverageSnapshots.date)).all();
|
|
77424
|
+
const bing = app.db.select({ date: bingCoverageSnapshots.date, indexed: bingCoverageSnapshots.indexed, notIndexed: bingCoverageSnapshots.notIndexed }).from(bingCoverageSnapshots).where(eq112(bingCoverageSnapshots.projectId, projectId)).orderBy(asc28(bingCoverageSnapshots.date)).all();
|
|
77214
77425
|
const coverage = (key, label, rows) => ({
|
|
77215
77426
|
key,
|
|
77216
77427
|
label,
|
|
@@ -77222,7 +77433,7 @@ function readSurface(app, projectId, surface) {
|
|
|
77222
77433
|
return [coverage("google", "Google", google), coverage("bing", "Bing", bing)];
|
|
77223
77434
|
}
|
|
77224
77435
|
case TrendSurfaces["site-health"]: {
|
|
77225
|
-
const rows = app.db.select({ auditedAt: siteAuditSnapshots.auditedAt, aggregateScore: siteAuditSnapshots.aggregateScore, pagesAudited: siteAuditSnapshots.pagesAudited }).from(siteAuditSnapshots).innerJoin(runs,
|
|
77436
|
+
const rows = app.db.select({ auditedAt: siteAuditSnapshots.auditedAt, aggregateScore: siteAuditSnapshots.aggregateScore, pagesAudited: siteAuditSnapshots.pagesAudited }).from(siteAuditSnapshots).innerJoin(runs, eq112(siteAuditSnapshots.runId, runs.id)).where(and95(...surfaceableSiteAuditFilters(projectId))).orderBy(asc28(siteAuditSnapshots.auditedAt)).all();
|
|
77226
77437
|
return [{
|
|
77227
77438
|
key: "site-health",
|
|
77228
77439
|
label: "Site Health",
|
|
@@ -77234,7 +77445,7 @@ function readSurface(app, projectId, surface) {
|
|
|
77234
77445
|
}
|
|
77235
77446
|
case TrendSurfaces.backlinks: {
|
|
77236
77447
|
const totals = computeFilteredReleaseTotals(app.db, projectId, BacklinkSources.commoncrawl);
|
|
77237
|
-
const rows = app.db.select({ release: backlinkSummaries.release, totalLinkingDomains: backlinkSummaries.totalLinkingDomains, queriedAt: backlinkSummaries.queriedAt }).from(backlinkSummaries).where(
|
|
77448
|
+
const rows = app.db.select({ release: backlinkSummaries.release, totalLinkingDomains: backlinkSummaries.totalLinkingDomains, queriedAt: backlinkSummaries.queriedAt }).from(backlinkSummaries).where(and95(eq112(backlinkSummaries.projectId, projectId), eq112(backlinkSummaries.source, BacklinkSources.commoncrawl))).all().sort((a, b) => compareReleaseIds(a.release, b.release)).map((row) => {
|
|
77238
77449
|
const filtered = totals.get(row.release);
|
|
77239
77450
|
const known = (filtered?.domainRows ?? 0) > 0 || row.totalLinkingDomains === 0;
|
|
77240
77451
|
const windowEnd = releaseWindowEndMonth(row.release);
|
|
@@ -77264,7 +77475,7 @@ function readSurface(app, projectId, surface) {
|
|
|
77264
77475
|
// ../api-routes/src/research.ts
|
|
77265
77476
|
import crypto60 from "crypto";
|
|
77266
77477
|
import { z as z7 } from "zod";
|
|
77267
|
-
import { and as
|
|
77478
|
+
import { and as and96, count as count9, desc as desc51, eq as eq113, gte as gte33, lt as lt20, or as or22, sql as sql46 } from "drizzle-orm";
|
|
77268
77479
|
var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
|
|
77269
77480
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
77270
77481
|
async function researchRoutes(app, opts) {
|
|
@@ -77283,7 +77494,7 @@ async function researchRoutes(app, opts) {
|
|
|
77283
77494
|
const input = parsed.data;
|
|
77284
77495
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
77285
77496
|
const requestHash2 = directResearchRequestHash(input);
|
|
77286
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
77497
|
+
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and96(eq113(researchRuns.projectId, project.id), eq113(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
|
|
77287
77498
|
if (existingReceipt) {
|
|
77288
77499
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
77289
77500
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -77318,7 +77529,7 @@ async function researchRoutes(app, opts) {
|
|
|
77318
77529
|
const initiatedBy = researchPrincipal(request);
|
|
77319
77530
|
const decision = app.db.transaction((tx) => {
|
|
77320
77531
|
if (input.idempotencyKey) {
|
|
77321
|
-
const existing = tx.select().from(researchRuns).where(
|
|
77532
|
+
const existing = tx.select().from(researchRuns).where(and96(eq113(researchRuns.projectId, project.id), eq113(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
77322
77533
|
if (existing) {
|
|
77323
77534
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
77324
77535
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -77326,10 +77537,10 @@ async function researchRoutes(app, opts) {
|
|
|
77326
77537
|
}
|
|
77327
77538
|
if (initiatedBy?.limited) {
|
|
77328
77539
|
const { start, end, date } = utcDayBounds(now);
|
|
77329
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
77330
|
-
|
|
77540
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and96(
|
|
77541
|
+
eq113(researchRuns.projectId, project.id),
|
|
77331
77542
|
gte33(researchRuns.createdAt, start),
|
|
77332
|
-
|
|
77543
|
+
lt20(researchRuns.createdAt, end),
|
|
77333
77544
|
sql46`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
77334
77545
|
)).get()?.value ?? 0;
|
|
77335
77546
|
if (used >= viewerDailyRunLimit) {
|
|
@@ -77358,15 +77569,15 @@ async function researchRoutes(app, opts) {
|
|
|
77358
77569
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
77359
77570
|
const requestHash2 = researchBatchRequestHash(input);
|
|
77360
77571
|
const receiptKeys = input.runs.map((_run, index) => batchChildReceiptKey(input.idempotencyKey, index));
|
|
77361
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
77362
|
-
|
|
77363
|
-
|
|
77572
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and96(
|
|
77573
|
+
eq113(researchRuns.projectId, project.id),
|
|
77574
|
+
eq113(researchRuns.idempotencyKey, receiptKeys[0])
|
|
77364
77575
|
)).get();
|
|
77365
77576
|
if (firstReceipt) {
|
|
77366
77577
|
if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
77367
|
-
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(
|
|
77368
|
-
|
|
77369
|
-
|
|
77578
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and96(
|
|
77579
|
+
eq113(researchRuns.projectId, project.id),
|
|
77580
|
+
eq113(researchRuns.idempotencyKey, key)
|
|
77370
77581
|
)).get());
|
|
77371
77582
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
77372
77583
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -77382,10 +77593,10 @@ async function researchRoutes(app, opts) {
|
|
|
77382
77593
|
const decision = app.db.transaction((tx) => {
|
|
77383
77594
|
if (initiatedBy?.limited) {
|
|
77384
77595
|
const { start, end, date } = utcDayBounds(now);
|
|
77385
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
77386
|
-
|
|
77596
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and96(
|
|
77597
|
+
eq113(researchRuns.projectId, project.id),
|
|
77387
77598
|
gte33(researchRuns.createdAt, start),
|
|
77388
|
-
|
|
77599
|
+
lt20(researchRuns.createdAt, end),
|
|
77389
77600
|
sql46`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
77390
77601
|
)).get()?.value ?? 0;
|
|
77391
77602
|
if (used + prepared.length > viewerDailyRunLimit) throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
|
|
@@ -77437,9 +77648,9 @@ async function researchRoutes(app, opts) {
|
|
|
77437
77648
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
77438
77649
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
77439
77650
|
const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
|
|
77440
|
-
const rows = app.db.select().from(researchRuns).where(
|
|
77441
|
-
|
|
77442
|
-
cursor ? or22(
|
|
77651
|
+
const rows = app.db.select().from(researchRuns).where(and96(
|
|
77652
|
+
eq113(researchRuns.projectId, project.id),
|
|
77653
|
+
cursor ? or22(lt20(researchRuns.createdAt, cursor.createdAt), and96(eq113(researchRuns.createdAt, cursor.createdAt), lt20(researchRuns.id, cursor.id))) : void 0
|
|
77443
77654
|
)).orderBy(desc51(researchRuns.createdAt), desc51(researchRuns.id)).limit(limit + 1).all();
|
|
77444
77655
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
77445
77656
|
const last = runs2.at(-1);
|
|
@@ -77589,9 +77800,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
77589
77800
|
return stored;
|
|
77590
77801
|
}
|
|
77591
77802
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
77592
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
77593
|
-
|
|
77594
|
-
|
|
77803
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and96(
|
|
77804
|
+
eq113(measurementQueryTemplates.projectId, projectId),
|
|
77805
|
+
eq113(measurementQueryTemplates.id, selection.templateId)
|
|
77595
77806
|
)).get();
|
|
77596
77807
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
77597
77808
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -77600,7 +77811,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
77600
77811
|
}
|
|
77601
77812
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
77602
77813
|
if (bindingLocation) {
|
|
77603
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
77814
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq113(projects.id, projectId)).get();
|
|
77604
77815
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
77605
77816
|
}
|
|
77606
77817
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -77613,9 +77824,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
77613
77824
|
return queries2.map((query2) => classifier?.classify(query2) ?? null);
|
|
77614
77825
|
}
|
|
77615
77826
|
function getDetail(app, projectId, id) {
|
|
77616
|
-
const row = app.db.select().from(researchRuns).where(
|
|
77827
|
+
const row = app.db.select().from(researchRuns).where(and96(eq113(researchRuns.id, id), eq113(researchRuns.projectId, projectId))).get();
|
|
77617
77828
|
if (!row) throw notFound("Research run", id);
|
|
77618
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
77829
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq113(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
77619
77830
|
return { ...serializeRun(row), queries: queries2 };
|
|
77620
77831
|
}
|
|
77621
77832
|
function serializeRun(row) {
|
|
@@ -77670,7 +77881,7 @@ function utcDayBounds(now) {
|
|
|
77670
77881
|
|
|
77671
77882
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
77672
77883
|
import crypto61 from "crypto";
|
|
77673
|
-
import { and as
|
|
77884
|
+
import { and as and97, eq as eq114 } from "drizzle-orm";
|
|
77674
77885
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
77675
77886
|
return db.transaction((tx) => {
|
|
77676
77887
|
const run = tx.select({
|
|
@@ -77678,7 +77889,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
77678
77889
|
trigger: runs.trigger,
|
|
77679
77890
|
status: runs.status,
|
|
77680
77891
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
77681
|
-
}).from(runs).where(
|
|
77892
|
+
}).from(runs).where(and97(eq114(runs.projectId, input.projectId), eq114(runs.id, input.runId))).get();
|
|
77682
77893
|
if (!run) throw notFound("Run", input.runId);
|
|
77683
77894
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
77684
77895
|
return null;
|
|
@@ -77699,7 +77910,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
77699
77910
|
throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
|
|
77700
77911
|
}
|
|
77701
77912
|
}
|
|
77702
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
77913
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and97(eq114(simpleMeasurementDefinitions.projectId, input.projectId), eq114(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
77703
77914
|
if (existing) {
|
|
77704
77915
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
77705
77916
|
const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
|
|
@@ -77725,15 +77936,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
77725
77936
|
if (run.status !== RunStatuses.running) {
|
|
77726
77937
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
77727
77938
|
}
|
|
77728
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
77939
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq114(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
77729
77940
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
77730
77941
|
}
|
|
77731
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
77942
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq114(queries.projectId, input.projectId)).all().map((query2) => query2.id));
|
|
77732
77943
|
if (definition2.queries.some((query2) => !projectQueryIds.has(query2.queryId))) {
|
|
77733
77944
|
throw validationError("Every captured query must belong to the run project.");
|
|
77734
77945
|
}
|
|
77735
77946
|
if (definition2.competitors !== void 0) {
|
|
77736
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
77947
|
+
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq114(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
|
|
77737
77948
|
const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
|
|
77738
77949
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
77739
77950
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|