@canonry/canonry 7.94.0 → 7.95.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/canonry/references/canonry-cli.md +6 -4
- package/assets/assets/{AgentSetupPage-BlPXN_oH.js → AgentSetupPage-BV0TvyWO.js} +4 -4
- package/assets/assets/{AuditHistoryPanel-N8Z4gng7.js → AuditHistoryPanel-CTVhEVjr.js} +1 -1
- package/assets/assets/{BacklinksPage-DSrgqlkj.js → BacklinksPage-CE1Bfpf6.js} +1 -1
- package/assets/assets/{HistoryPage-v2jbUkle.js → HistoryPage-BhqA-Udl.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-BiJ06H7w.js +4 -0
- package/assets/assets/ProjectPage-BqS0rfDT.js +11 -0
- package/assets/assets/{RunsPage-CXZJDdnr.js → RunsPage-BII3_JsE.js} +1 -1
- package/assets/assets/{SettingsPage-CeLvWs_T.js → SettingsPage-DS4ivpsX.js} +1 -1
- package/assets/assets/SiteHealthSection-IywsGGIA.js +2 -0
- package/assets/assets/ToneBadge-CTZ_JZLD.js +60 -0
- package/assets/assets/{TrafficPage-CEsmS5N1.js → TrafficPage-Bg9JrnxC.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-D31g1FXV.js → TrafficSourceDetailPage-BuD8BlaT.js} +1 -1
- package/assets/assets/{account-context-CGIyok0O.js → account-context-ylqeghOr.js} +1 -1
- package/assets/assets/{agent-trust-copy-B-3akxrd.js → agent-trust-copy-DxpavxIA.js} +1 -1
- package/assets/assets/{card-BMjN_ofC.js → card-rZ7BA4sl.js} +1 -1
- package/assets/assets/{format-helpers-Blu0QNcd.js → format-helpers-PdOXn5Yf.js} +1 -1
- package/assets/assets/index-CU7tAhKY.js +60 -0
- package/assets/assets/{query-client-DdwTc28X.js → query-client-Cq4kP41m.js} +1 -1
- package/assets/assets/react-query-DVctucqC.js +1 -0
- package/assets/assets/{react-sigma_core.esm.min-5sP0Pb_F.js → react-sigma_core.esm.min-DYhAEr2t.js} +1 -1
- package/assets/assets/{server-traffic-403Q1CKm.js → server-traffic-Owp6jMwT.js} +1 -1
- package/assets/assets/{service-ChEbo8oM.js → service-y3nQY3Ue.js} +1 -1
- package/assets/index.html +7 -7
- package/dist/{ads-6RVS23I5.js → ads-GN7QSEUV.js} +3 -3
- package/dist/{agent-N5LH7QNA.js → agent-J46OIQHH.js} +7 -7
- package/dist/{agent-ask-IJV2PWPN.js → agent-ask-755DZ4YB.js} +3 -3
- package/dist/{agent-conversations-STISIDWB.js → agent-conversations-QO7C6F6L.js} +3 -3
- package/dist/{agent-journeys-VCP5MD2W.js → agent-journeys-SVI6LWKM.js} +3 -3
- package/dist/{agent-memory-XQQNXRY6.js → agent-memory-V3ZJZT5W.js} +3 -3
- package/dist/{agent-providers-IW2Q6WPP.js → agent-providers-QIHZ222U.js} +3 -3
- package/dist/{agent-transcript-5XUCHOOT.js → agent-transcript-6TS3ZOCA.js} +3 -3
- package/dist/{analytics-RZZZBC4H.js → analytics-LFT5OAOR.js} +3 -3
- package/dist/{apply-X5ZTHGSJ.js → apply-3XXMVLBK.js} +3 -3
- package/dist/{backfill-ZF6RX27O.js → backfill-F62Y47XV.js} +10 -10
- package/dist/{backlinks-WZ7YUKHH.js → backlinks-AOA5KHHB.js} +3 -3
- package/dist/{bing-Z4A5NHOC.js → bing-75F4W4E2.js} +3 -3
- package/dist/{bootstrap-OCJ7EIXK.js → bootstrap-CDGVAICZ.js} +10 -10
- package/dist/{cdp-YGZFLTFT.js → cdp-YBCWRWY4.js} +7 -7
- package/dist/{chunk-YRCKRY3Q.js → chunk-46YXUJSW.js} +2 -2
- package/dist/{chunk-YGHI3W4D.js → chunk-5TPLZD4I.js} +1 -1
- package/dist/{chunk-SFNSSHIN.js → chunk-6RDBULDN.js} +1 -1
- package/dist/{chunk-N6MXZ5SC.js → chunk-6S6XEEGX.js} +5 -5
- package/dist/{chunk-XIHIB7VP.js → chunk-73AUPWSG.js} +6 -6
- package/dist/{chunk-N7PGV6QQ.js → chunk-7PUUMJXY.js} +922 -770
- package/dist/{chunk-JDOAULVX.js → chunk-A6O6X6FU.js} +1 -1
- package/dist/{chunk-D6KTO5TO.js → chunk-BUZBBVDZ.js} +1 -1
- package/dist/{chunk-PR5FCYNN.js → chunk-C6UU2PDJ.js} +21 -21
- package/dist/{chunk-NSUB2S4V.js → chunk-CVCUADCM.js} +14 -11
- package/dist/{chunk-CBNDEKI6.js → chunk-EWSVH5TU.js} +1 -1
- package/dist/{chunk-3D4VEWDJ.js → chunk-FESPLVBO.js} +6 -6
- package/dist/{chunk-B5JKQXRN.js → chunk-FWY5HUSB.js} +2 -2
- package/dist/{chunk-S6KJYLOV.js → chunk-GUVN5OSX.js} +3 -3
- package/dist/{chunk-K63XX7VY.js → chunk-IATTIKXF.js} +3 -3
- package/dist/{chunk-BL4CZJYJ.js → chunk-J3NS6NGM.js} +115 -3
- package/dist/{chunk-IUDXV6PH.js → chunk-JVOOKVCW.js} +2 -2
- package/dist/{chunk-FM74QGW5.js → chunk-KK7NWOGH.js} +7 -7
- package/dist/{chunk-BJAOD3NW.js → chunk-KMT7I5KZ.js} +5 -5
- package/dist/{chunk-K2JCPF2W.js → chunk-L7W72K4X.js} +367 -367
- package/dist/{chunk-55EFIQP7.js → chunk-LHU3KIKG.js} +2 -2
- package/dist/{chunk-QDGW75MP.js → chunk-MKLAX5KY.js} +1 -1
- package/dist/{chunk-LXJAXAJD.js → chunk-NJG6WFRK.js} +1 -1
- package/dist/{chunk-XPBEDIXW.js → chunk-NMUL63XG.js} +2 -2
- package/dist/{chunk-UPBQHUE6.js → chunk-NVWTGVZ4.js} +3 -3
- package/dist/{chunk-LDY66O7A.js → chunk-PHOMW3R4.js} +4 -4
- package/dist/{chunk-M6DYUCNB.js → chunk-Q6WHXY7D.js} +1 -1
- package/dist/{chunk-VGAQH7KI.js → chunk-RPPB4H4D.js} +1 -1
- package/dist/{chunk-RJLSGCOI.js → chunk-T3FYWUQL.js} +2 -2
- package/dist/{chunk-ECUJEKIO.js → chunk-TG5D4E2Y.js} +1 -1
- package/dist/{chunk-32N7BGTD.js → chunk-TS5Q63NE.js} +3 -3
- package/dist/{chunk-RSBIHKHH.js → chunk-TXW2FPNJ.js} +4 -4
- package/dist/{chunk-7JQM46YO.js → chunk-VOP74COR.js} +2 -2
- package/dist/{chunk-NUBADAA4.js → chunk-WK6IPTTM.js} +4 -4
- package/dist/{chunk-R7BX2ZSQ.js → chunk-YTRXALSU.js} +1 -1
- package/dist/{citations-IUITVPHO.js → citations-SQYZTJQ6.js} +3 -3
- package/dist/{cli-catalog-IAPKU2RG.js → cli-catalog-N3JLEU77.js} +3 -3
- package/dist/{cli-commands-FICINBUR.js → cli-commands-QOJJMX2X.js} +7 -7
- package/dist/cli.js +13 -13
- package/dist/{competitor-U5DMMCIA.js → competitor-C5JAHHR7.js} +3 -3
- package/dist/{content-ZQIZUNF5.js → content-RVPODQI6.js} +3 -3
- package/dist/{dashboard-KPGX4EQT.js → dashboard-3NAY2SX6.js} +6 -6
- package/dist/{demo-NEWVCEXD.js → demo-OSYK452E.js} +1 -1
- package/dist/{demo-server-5GJEGG5K.js → demo-server-GO4V5UXK.js} +14 -14
- package/dist/{discover-HSPVFHKK.js → discover-U2DNK2TO.js} +3 -3
- package/dist/{discover-eval-BYKPJ3DL.js → discover-eval-ESLS56AH.js} +3 -3
- package/dist/{doctor-CK4BTTWH.js → doctor-SBF54WXE.js} +3 -3
- package/dist/{evidence-BX3CWPCT.js → evidence-WHDCM7Z5.js} +3 -3
- package/dist/{export-cmd-QMCRDMPZ.js → export-cmd-7CHS6K3S.js} +3 -3
- package/dist/{ga-RMTUMSEJ.js → ga-XRASDAVX.js} +4 -4
- package/dist/{gbp-JK67SYLW.js → gbp-FEOFMTBH.js} +3 -3
- package/dist/{google-PJSK6IJV.js → google-MWJ4HAUB.js} +4 -4
- package/dist/{google-marketing-UQ2FONXU.js → google-marketing-GELTQ32D.js} +1 -1
- package/dist/{health-cmd-ZDXFD53P.js → health-cmd-IO7YMTRQ.js} +3 -3
- package/dist/{history-OYL7SPEY.js → history-I74D3NTT.js} +3 -3
- package/dist/index.d.ts +194 -16
- package/dist/index.js +25 -25
- package/dist/{init-DZUNAQBN.js → init-ZLJMCJ3Q.js} +12 -12
- package/dist/{insights-AIBLDPX6.js → insights-EAMVEB5Y.js} +3 -3
- package/dist/intelligence-service-GSQ7YAYU.js +16 -0
- package/dist/{journeys-EDEET2EC.js → journeys-E373TZZS.js} +3 -3
- package/dist/{keys-SEZQTYGJ.js → keys-27JRRWKZ.js} +3 -3
- package/dist/{keyword-6FJIWTHW.js → keyword-45H2TBRG.js} +3 -3
- package/dist/{logs-EML2KKOP.js → logs-K4K4OJDY.js} +3 -3
- package/dist/{mcp-AA5QUNBZ.js → mcp-734WXNIA.js} +4 -4
- package/dist/mcp.js +9 -9
- package/dist/{measurement-plan-ALHRGRPH.js → measurement-plan-XZXK3YLO.js} +5 -5
- package/dist/{notify-EVTI3SWQ.js → notify-ZGLVTSVC.js} +3 -3
- package/dist/{organic-evidence-EXQQ6R7H.js → organic-evidence-CRTYFYAN.js} +3 -3
- package/dist/{overview-5CIFLSDG.js → overview-5URBJKI4.js} +4 -4
- package/dist/{project-DNDENHRU.js → project-R5W4Y4L4.js} +3 -3
- package/dist/{query-ETMHFVKZ.js → query-LPGNI3ZR.js} +3 -3
- package/dist/{research-Q5X46CR5.js → research-GTVKPIO2.js} +3 -3
- package/dist/{results-export-DZZANVYY.js → results-export-UNQQHELR.js} +3 -3
- package/dist/{run-OQJXRJ42.js → run-DLAFXVF4.js} +4 -4
- package/dist/{schedule-6AI3J2AB.js → schedule-7I5VIJFS.js} +3 -3
- package/dist/{search-7TAJBSHE.js → search-MUS7FSOJ.js} +3 -3
- package/dist/{serve-WD44V3TK.js → serve-5TV26VAH.js} +26 -26
- package/dist/{session-LTKCS763.js → session-FWALDL3F.js} +13 -13
- package/dist/{settings-EUMYGNIV.js → settings-EB433IDI.js} +3 -3
- package/dist/{sitemaps-I4QPLAZW.js → sitemaps-5EVGGGQ5.js} +3 -3
- package/dist/{skills-PTYYU3FR.js → skills-2HS4MYNT.js} +2 -2
- package/dist/{snapshot-62FF4GEA.js → snapshot-4DMW7AK2.js} +3 -3
- package/dist/{src-LRYXRHLW.js → src-OYMKXD6L.js} +4 -2
- package/dist/{status-VRWPYTY3.js → status-2CRLY5B6.js} +4 -4
- package/dist/{technical-aeo-OKZW2GAX.js → technical-aeo-HIQJCEML.js} +3 -3
- package/dist/{telemetry-RRTHKD6W.js → telemetry-H3SGPKZC.js} +4 -4
- package/dist/{tools-F7WCCKZW.js → tools-PHYD6WYF.js} +11 -11
- package/dist/{traffic-H6RDZWGG.js → traffic-47L2EV6Q.js} +5 -5
- package/dist/{trends-RZOQW2LE.js → trends-CCWE7MF6.js} +3 -3
- package/dist/{users-KFBCBA3V.js → users-BGIGSKB2.js} +3 -3
- package/dist/{visibility-stats-53OZR3UN.js → visibility-stats-XVF64BMX.js} +3 -3
- package/dist/{wordpress-2BXU6FDI.js → wordpress-DQ6DAQPS.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-MKLAX5KY.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-EWSVH5TU.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-J3NS6NGM.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,
|
|
@@ -1280,7 +1283,7 @@ import {
|
|
|
1280
1283
|
wordpressSchemaDeployResultDtoSchema,
|
|
1281
1284
|
wordpressSchemaStatusResultDtoSchema,
|
|
1282
1285
|
wordpressStatusDtoSchema
|
|
1283
|
-
} from "./chunk-
|
|
1286
|
+
} from "./chunk-CVCUADCM.js";
|
|
1284
1287
|
|
|
1285
1288
|
// ../api-routes/src/user-session.ts
|
|
1286
1289
|
import crypto2 from "crypto";
|
|
@@ -36493,11 +36496,15 @@ var analyticsWindowParameter = {
|
|
|
36493
36496
|
description: "Time window for analytics queries. An unrecognised value is rejected with 400; it is never widened to the full history.",
|
|
36494
36497
|
schema: { type: "string", enum: ["7d", "30d", "90d", "all"] }
|
|
36495
36498
|
};
|
|
36499
|
+
var gaTrafficWindowParameter = {
|
|
36500
|
+
...analyticsWindowParameter,
|
|
36501
|
+
schema: { type: "string", enum: [...WINDOW_SETS.gaTraffic] }
|
|
36502
|
+
};
|
|
36496
36503
|
var gaOverviewWindowParameter = {
|
|
36497
36504
|
name: "window",
|
|
36498
36505
|
in: "query",
|
|
36499
36506
|
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: [
|
|
36507
|
+
schema: { type: "string", enum: [...WINDOW_SETS.gaOverview] }
|
|
36501
36508
|
};
|
|
36502
36509
|
var competitorLandscapeGroupKeyParameter = {
|
|
36503
36510
|
name: "groupKey",
|
|
@@ -42757,7 +42764,7 @@ var routeCatalog = [
|
|
|
42757
42764
|
{
|
|
42758
42765
|
name: "window",
|
|
42759
42766
|
in: "query",
|
|
42760
|
-
description: `Stored window, ending yesterday in the GA4 property time zone. 28d matches GA4's default "Last 28 days".`,
|
|
42767
|
+
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
42768
|
schema: { type: "string", enum: [...gaSearchLandingWindowSchema.options], default: "28d" }
|
|
42762
42769
|
},
|
|
42763
42770
|
{
|
|
@@ -42812,7 +42819,7 @@ var routeCatalog = [
|
|
|
42812
42819
|
summary: "Get GA4 landing page traffic, channel breakdown, and AI referral landing pages",
|
|
42813
42820
|
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
42821
|
tags: ["ga4"],
|
|
42815
|
-
parameters: [nameParameter, limitQueryParameter,
|
|
42822
|
+
parameters: [nameParameter, limitQueryParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
|
|
42816
42823
|
responses: {
|
|
42817
42824
|
200: jsonResponse("GA4 traffic data returned.", "GaTrafficResponse"),
|
|
42818
42825
|
400: errorResponse("GA4 is not connected."),
|
|
@@ -42825,7 +42832,7 @@ var routeCatalog = [
|
|
|
42825
42832
|
summary: "Get raw AI referral detail rows per day, landing page, and attribution dimension",
|
|
42826
42833
|
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
42834
|
tags: ["ga4"],
|
|
42828
|
-
parameters: [nameParameter,
|
|
42835
|
+
parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
|
|
42829
42836
|
responses: {
|
|
42830
42837
|
200: jsonArrayResponse("AI referral history returned.", "GA4AiReferralHistoryEntry"),
|
|
42831
42838
|
400: errorResponse("GA4 is not connected."),
|
|
@@ -42838,7 +42845,7 @@ var routeCatalog = [
|
|
|
42838
42845
|
summary: "Get AI referral sessions per day and per source",
|
|
42839
42846
|
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
42847
|
tags: ["ga4"],
|
|
42841
|
-
parameters: [nameParameter,
|
|
42848
|
+
parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
|
|
42842
42849
|
responses: {
|
|
42843
42850
|
200: jsonResponse("AI referral daily series returned.", "GA4AiReferralDailyDto"),
|
|
42844
42851
|
400: errorResponse("GA4 is not connected."),
|
|
@@ -42850,7 +42857,7 @@ var routeCatalog = [
|
|
|
42850
42857
|
path: "/api/v1/projects/{name}/ga/social-referral-history",
|
|
42851
42858
|
summary: "Get social media referral sessions per day grouped by source",
|
|
42852
42859
|
tags: ["ga4"],
|
|
42853
|
-
parameters: [nameParameter,
|
|
42860
|
+
parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
|
|
42854
42861
|
responses: {
|
|
42855
42862
|
200: jsonArrayResponse("Social referral history returned.", "GA4SocialReferralHistoryEntry"),
|
|
42856
42863
|
400: errorResponse("GA4 is not connected."),
|
|
@@ -42891,7 +42898,7 @@ var routeCatalog = [
|
|
|
42891
42898
|
parameters: [nameParameter, gaOverviewWindowParameter],
|
|
42892
42899
|
responses: {
|
|
42893
42900
|
200: jsonResponse("GA4 overview returned.", "GaOverviewResponse"),
|
|
42894
|
-
400: errorResponse("GA4 is not connected, or the window is not 7d, 30d or
|
|
42901
|
+
400: errorResponse("GA4 is not connected, or the window is not 7d, 30d, 90d or 180d."),
|
|
42895
42902
|
404: errorResponse("Project not found.")
|
|
42896
42903
|
}
|
|
42897
42904
|
},
|
|
@@ -42900,7 +42907,7 @@ var routeCatalog = [
|
|
|
42900
42907
|
path: "/api/v1/projects/{name}/ga/session-history",
|
|
42901
42908
|
summary: "Get total sessions per day for the project",
|
|
42902
42909
|
tags: ["ga4"],
|
|
42903
|
-
parameters: [nameParameter,
|
|
42910
|
+
parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
|
|
42904
42911
|
responses: {
|
|
42905
42912
|
200: jsonArrayResponse("Session history returned.", "GA4SessionHistoryEntry"),
|
|
42906
42913
|
400: errorResponse("GA4 is not connected."),
|
|
@@ -47697,6 +47704,7 @@ var GA_PROPERTY_TABLES = [
|
|
|
47697
47704
|
gaTrafficWindowSummaries,
|
|
47698
47705
|
gaWindowMetrics,
|
|
47699
47706
|
gaDailyTotals,
|
|
47707
|
+
gaSyncCoverage,
|
|
47700
47708
|
gaAiReferrals,
|
|
47701
47709
|
gaSocialReferrals,
|
|
47702
47710
|
gaAcquisitionDaily,
|
|
@@ -47781,7 +47789,7 @@ var GA4_ADMIN_API_BASE = "https://analyticsadmin.googleapis.com/v1beta";
|
|
|
47781
47789
|
var GA4_SCOPE = "https://www.googleapis.com/auth/analytics.readonly";
|
|
47782
47790
|
var GOOGLE_TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
47783
47791
|
var GA4_DEFAULT_SYNC_DAYS = 30;
|
|
47784
|
-
var GA4_MAX_SYNC_DAYS =
|
|
47792
|
+
var GA4_MAX_SYNC_DAYS = 180;
|
|
47785
47793
|
function resolveGa4SyncDays(days) {
|
|
47786
47794
|
const requestedDays = days != null && Number.isFinite(days) ? Math.trunc(days) : GA4_DEFAULT_SYNC_DAYS;
|
|
47787
47795
|
const effectiveDays = Math.min(Math.max(1, requestedDays), GA4_MAX_SYNC_DAYS);
|
|
@@ -47859,6 +47867,7 @@ var GA4_METRICS = {
|
|
|
47859
47867
|
};
|
|
47860
47868
|
var GA4_SEARCH_LANDING_MAX_ROWS = 1e4;
|
|
47861
47869
|
var GA4_SEARCH_LANDING_PAGE_SIZE = 1e4;
|
|
47870
|
+
var GA4_SEARCH_LANDING_REPORT_DEFINITION = "search-landing-v1";
|
|
47862
47871
|
|
|
47863
47872
|
// ../integration-google-analytics/src/types.ts
|
|
47864
47873
|
var GA4ApiError = class extends Error {
|
|
@@ -48163,7 +48172,12 @@ function reportTimeZone(...responses) {
|
|
|
48163
48172
|
}
|
|
48164
48173
|
return null;
|
|
48165
48174
|
}
|
|
48166
|
-
function measurementDateRange(days) {
|
|
48175
|
+
function measurementDateRange(days, requestedRange, maxDays = GA4_MAX_SYNC_DAYS) {
|
|
48176
|
+
if (requestedRange) {
|
|
48177
|
+
const count10 = inclusiveDayCount(requestedRange.startDate, requestedRange.endDate);
|
|
48178
|
+
if (count10 === null || count10 > maxDays + 1) throw new GA4ApiError("Invalid GA4 sync date range", 400);
|
|
48179
|
+
return { syncDays: count10, dateRange: requestedRange };
|
|
48180
|
+
}
|
|
48167
48181
|
const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
|
|
48168
48182
|
const endDate = /* @__PURE__ */ new Date();
|
|
48169
48183
|
const startDate = /* @__PURE__ */ new Date();
|
|
@@ -48211,7 +48225,7 @@ async function fetchPagedReportWithTotals(accessToken, propertyId, baseRequest,
|
|
|
48211
48225
|
async function fetchAcquisitionByChannel(accessToken, propertyId, days, options) {
|
|
48212
48226
|
validateAccessToken2(accessToken);
|
|
48213
48227
|
validatePropertyId(propertyId);
|
|
48214
|
-
const { syncDays, dateRange } = measurementDateRange(days);
|
|
48228
|
+
const { syncDays, dateRange } = measurementDateRange(days, options?.dateRange);
|
|
48215
48229
|
const rows = await fetchPagedReport(accessToken, propertyId, {
|
|
48216
48230
|
dateRanges: [dateRange],
|
|
48217
48231
|
dimensions: [
|
|
@@ -48238,10 +48252,10 @@ async function fetchAcquisitionByChannel(accessToken, propertyId, days, options)
|
|
|
48238
48252
|
function isLandingPageDimensionIncompatibility(error) {
|
|
48239
48253
|
return error instanceof GA4ApiError && error.status === 400 && /landingPagePlusQueryString/i.test(error.message) && /incompatib/i.test(error.message);
|
|
48240
48254
|
}
|
|
48241
|
-
async function fetchLeadEvents(accessToken, propertyId, eventNames, days) {
|
|
48255
|
+
async function fetchLeadEvents(accessToken, propertyId, eventNames, days, dateRangeOverride) {
|
|
48242
48256
|
validateAccessToken2(accessToken);
|
|
48243
48257
|
validatePropertyId(propertyId);
|
|
48244
|
-
const { dateRange } = measurementDateRange(days);
|
|
48258
|
+
const { dateRange } = measurementDateRange(days, dateRangeOverride);
|
|
48245
48259
|
const filter = { orGroup: { expressions: eventNames.map((value) => ({ filter: {
|
|
48246
48260
|
fieldName: GA4_DIMENSIONS.eventName,
|
|
48247
48261
|
stringFilter: { matchType: "EXACT", value }
|
|
@@ -48327,13 +48341,10 @@ var AI_REFERRAL_PASSES = [
|
|
|
48327
48341
|
function isInvalidGa4Request(error) {
|
|
48328
48342
|
return error instanceof GA4ApiError && error.status === 400;
|
|
48329
48343
|
}
|
|
48330
|
-
async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
48344
|
+
async function fetchTrafficByLandingPage(accessToken, propertyId, days, dateRangeOverride) {
|
|
48331
48345
|
validateAccessToken2(accessToken);
|
|
48332
48346
|
validatePropertyId(propertyId);
|
|
48333
|
-
const {
|
|
48334
|
-
const endDate = /* @__PURE__ */ new Date();
|
|
48335
|
-
const startDate = /* @__PURE__ */ new Date();
|
|
48336
|
-
startDate.setDate(startDate.getDate() - syncDays);
|
|
48347
|
+
const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
|
|
48337
48348
|
ga4Log("info", "fetch-traffic.start", { propertyId, days: syncDays });
|
|
48338
48349
|
const PAGE_SIZE = 1e4;
|
|
48339
48350
|
const rows = [];
|
|
@@ -48342,7 +48353,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48342
48353
|
while (pageCount < GA4_MAX_PAGES) {
|
|
48343
48354
|
pageCount++;
|
|
48344
48355
|
const request = {
|
|
48345
|
-
dateRanges: [
|
|
48356
|
+
dateRanges: [dateRange],
|
|
48346
48357
|
dimensions: [
|
|
48347
48358
|
{ name: GA4_DIMENSIONS.date },
|
|
48348
48359
|
{ name: GA4_DIMENSIONS.landingPagePlusQueryString }
|
|
@@ -48367,9 +48378,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48367
48378
|
users: parseInt(row.metricValues[1].value, 10) || 0
|
|
48368
48379
|
}));
|
|
48369
48380
|
rows.push(...pageRows);
|
|
48370
|
-
const totalRows = response.rowCount
|
|
48381
|
+
const totalRows = response.rowCount;
|
|
48371
48382
|
offset += pageRows.length;
|
|
48372
|
-
if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
|
|
48383
|
+
if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
|
|
48384
|
+
if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
|
|
48373
48385
|
}
|
|
48374
48386
|
const organicMap = /* @__PURE__ */ new Map();
|
|
48375
48387
|
let organicOffset = 0;
|
|
@@ -48377,7 +48389,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48377
48389
|
while (organicPageCount < GA4_MAX_PAGES) {
|
|
48378
48390
|
organicPageCount++;
|
|
48379
48391
|
const organicRequest = {
|
|
48380
|
-
dateRanges: [
|
|
48392
|
+
dateRanges: [dateRange],
|
|
48381
48393
|
dimensions: [{ name: GA4_DIMENSIONS.date }, { name: GA4_DIMENSIONS.landingPagePlusQueryString }],
|
|
48382
48394
|
metrics: [{ name: GA4_METRICS.sessions }],
|
|
48383
48395
|
dimensionFilter: {
|
|
@@ -48395,9 +48407,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48395
48407
|
const key = `${row.dimensionValues[0].value}::${row.dimensionValues[1].value}`;
|
|
48396
48408
|
organicMap.set(key, parseInt(row.metricValues[0].value, 10) || 0);
|
|
48397
48409
|
}
|
|
48398
|
-
const total = organicResponse.rowCount
|
|
48410
|
+
const total = organicResponse.rowCount;
|
|
48399
48411
|
organicOffset += (organicResponse.rows ?? []).length;
|
|
48400
|
-
if ((organicResponse.rows ?? []).length < 1e4 || organicOffset >= total) break;
|
|
48412
|
+
if ((organicResponse.rows ?? []).length < 1e4 || total !== void 0 && organicOffset >= total) break;
|
|
48413
|
+
if (organicPageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 organic report truncated at the page limit", 502);
|
|
48401
48414
|
}
|
|
48402
48415
|
const directMap = /* @__PURE__ */ new Map();
|
|
48403
48416
|
let directOffset = 0;
|
|
@@ -48405,7 +48418,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48405
48418
|
while (directPageCount < GA4_MAX_PAGES) {
|
|
48406
48419
|
directPageCount++;
|
|
48407
48420
|
const directRequest = {
|
|
48408
|
-
dateRanges: [
|
|
48421
|
+
dateRanges: [dateRange],
|
|
48409
48422
|
dimensions: [{ name: GA4_DIMENSIONS.date }, { name: GA4_DIMENSIONS.landingPagePlusQueryString }],
|
|
48410
48423
|
metrics: [{ name: GA4_METRICS.sessions }],
|
|
48411
48424
|
dimensionFilter: {
|
|
@@ -48423,9 +48436,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
|
48423
48436
|
const key = `${row.dimensionValues[0].value}::${row.dimensionValues[1].value}`;
|
|
48424
48437
|
directMap.set(key, parseInt(row.metricValues[0].value, 10) || 0);
|
|
48425
48438
|
}
|
|
48426
|
-
const total = directResponse.rowCount
|
|
48439
|
+
const total = directResponse.rowCount;
|
|
48427
48440
|
directOffset += (directResponse.rows ?? []).length;
|
|
48428
|
-
if ((directResponse.rows ?? []).length < 1e4 || directOffset >= total) break;
|
|
48441
|
+
if ((directResponse.rows ?? []).length < 1e4 || total !== void 0 && directOffset >= total) break;
|
|
48442
|
+
if (directPageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 direct report truncated at the page limit", 502);
|
|
48429
48443
|
}
|
|
48430
48444
|
for (const row of rows) {
|
|
48431
48445
|
const key = `${row.date}::${row.landingPage}`;
|
|
@@ -48592,7 +48606,8 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
|
|
|
48592
48606
|
validatePropertyId(propertyId);
|
|
48593
48607
|
const historyDays = Math.min(Math.max(1, Number.isFinite(days) ? Math.trunc(days) : GA_OVERVIEW_HISTORY_DAYS), GA_OVERVIEW_HISTORY_DAYS);
|
|
48594
48608
|
const requestedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
48595
|
-
const dateRange = { startDate: ga4RelativeDate(historyDays), endDate: ga4RelativeDate(0) };
|
|
48609
|
+
const dateRange = options.dateRange ? measurementDateRange(days, options.dateRange, GA_OVERVIEW_HISTORY_DAYS).dateRange : { startDate: ga4RelativeDate(historyDays), endDate: ga4RelativeDate(0) };
|
|
48610
|
+
const rowLimit = options.dateRange ? inclusiveDayCount(dateRange.startDate, dateRange.endDate) : historyDays + 1;
|
|
48596
48611
|
ga4Log("info", "fetch-daily-totals.start", { propertyId, days: historyDays });
|
|
48597
48612
|
const [totalsRes, organicRes] = await batchRunReports(accessToken, propertyId, [
|
|
48598
48613
|
{
|
|
@@ -48610,7 +48625,7 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
|
|
|
48610
48625
|
{ name: GA4_METRICS.userEngagementDuration },
|
|
48611
48626
|
{ name: GA4_METRICS.bounceRate }
|
|
48612
48627
|
],
|
|
48613
|
-
limit:
|
|
48628
|
+
limit: rowLimit
|
|
48614
48629
|
},
|
|
48615
48630
|
{
|
|
48616
48631
|
dateRanges: [dateRange],
|
|
@@ -48622,7 +48637,7 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
|
|
|
48622
48637
|
stringFilter: { matchType: "EXACT", value: "Organic Search" }
|
|
48623
48638
|
}
|
|
48624
48639
|
},
|
|
48625
|
-
limit:
|
|
48640
|
+
limit: rowLimit
|
|
48626
48641
|
}
|
|
48627
48642
|
]);
|
|
48628
48643
|
const organicByDate = /* @__PURE__ */ new Map();
|
|
@@ -48652,18 +48667,17 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
|
|
|
48652
48667
|
const timeZone = reportTimeZone(totalsRes, organicRes) ?? "UTC";
|
|
48653
48668
|
ga4Log("info", "fetch-daily-totals.done", { propertyId, days: historyDays, timeZone, rows: rows.length });
|
|
48654
48669
|
return {
|
|
48655
|
-
startDate: isoDateDaysBeforeInTimeZone(requestedAt, historyDays, timeZone),
|
|
48656
|
-
endDate: isoDateDaysBeforeInTimeZone(requestedAt, 0, timeZone),
|
|
48670
|
+
startDate: options.dateRange?.startDate ?? isoDateDaysBeforeInTimeZone(requestedAt, historyDays, timeZone),
|
|
48671
|
+
endDate: options.dateRange?.endDate ?? isoDateDaysBeforeInTimeZone(requestedAt, 0, timeZone),
|
|
48657
48672
|
rows
|
|
48658
48673
|
};
|
|
48659
48674
|
}
|
|
48660
|
-
var WINDOW_DAYS2 = { "7d": 7, "30d": 30, "90d": 90 };
|
|
48661
48675
|
async function fetchWindowSummary(accessToken, propertyId, windowKey) {
|
|
48662
48676
|
validateAccessToken2(accessToken);
|
|
48663
48677
|
validatePropertyId(propertyId);
|
|
48664
|
-
const days =
|
|
48678
|
+
const days = GA_OVERVIEW_WINDOW_DAYS[windowKey];
|
|
48665
48679
|
if (!days) {
|
|
48666
|
-
throw new GA4ApiError(`Unsupported windowKey "${windowKey}" \u2014 must be 7d, 30d, or
|
|
48680
|
+
throw new GA4ApiError(`Unsupported windowKey "${windowKey}" \u2014 must be 7d, 30d, 90d, or 180d`, 400);
|
|
48667
48681
|
}
|
|
48668
48682
|
const endDate = /* @__PURE__ */ new Date();
|
|
48669
48683
|
const startDate = /* @__PURE__ */ new Date();
|
|
@@ -48793,16 +48807,12 @@ async function fetchWindowMetrics(accessToken, propertyId, windowKey, options =
|
|
|
48793
48807
|
previous: period2(COMPARISON_RANGE_NAMES.previous, ranges.previous)
|
|
48794
48808
|
};
|
|
48795
48809
|
ga4Log("info", "fetch-window-metrics.done", { propertyId, windowKey, timeZone });
|
|
48796
|
-
return result;
|
|
48810
|
+
return { ...result, timeZone };
|
|
48797
48811
|
}
|
|
48798
|
-
async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
48812
|
+
async function fetchAiReferrals(accessToken, propertyId, days, dateRangeOverride) {
|
|
48799
48813
|
validateAccessToken2(accessToken);
|
|
48800
48814
|
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) };
|
|
48815
|
+
const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
|
|
48806
48816
|
ga4Log("info", "fetch-ai-referrals.start", { propertyId, days: syncDays });
|
|
48807
48817
|
const rows = [];
|
|
48808
48818
|
for (const pass of AI_REFERRAL_PASSES) {
|
|
@@ -48900,20 +48910,18 @@ async function fetchAiReferralPass(accessToken, propertyId, dateRange, pass, cha
|
|
|
48900
48910
|
};
|
|
48901
48911
|
});
|
|
48902
48912
|
rows.push(...pageRows);
|
|
48903
|
-
const totalRows = response.rowCount
|
|
48913
|
+
const totalRows = response.rowCount;
|
|
48904
48914
|
offset += pageRows.length;
|
|
48905
|
-
if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
|
|
48915
|
+
if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
|
|
48916
|
+
if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
|
|
48906
48917
|
}
|
|
48907
48918
|
return rows;
|
|
48908
48919
|
}
|
|
48909
48920
|
var SOCIAL_CHANNEL_GROUPS = ["Organic Social", "Paid Social"];
|
|
48910
|
-
async function fetchSocialReferrals(accessToken, propertyId, days) {
|
|
48921
|
+
async function fetchSocialReferrals(accessToken, propertyId, days, dateRangeOverride) {
|
|
48911
48922
|
validateAccessToken2(accessToken);
|
|
48912
48923
|
validatePropertyId(propertyId);
|
|
48913
|
-
const {
|
|
48914
|
-
const endDate = /* @__PURE__ */ new Date();
|
|
48915
|
-
const startDate = /* @__PURE__ */ new Date();
|
|
48916
|
-
startDate.setDate(startDate.getDate() - syncDays);
|
|
48924
|
+
const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
|
|
48917
48925
|
ga4Log("info", "fetch-social-referrals.start", { propertyId, days: syncDays });
|
|
48918
48926
|
const PAGE_SIZE = 1e3;
|
|
48919
48927
|
const rows = [];
|
|
@@ -48921,7 +48929,7 @@ async function fetchSocialReferrals(accessToken, propertyId, days) {
|
|
|
48921
48929
|
let pageCount = 0;
|
|
48922
48930
|
while (pageCount < GA4_MAX_PAGES) {
|
|
48923
48931
|
const request = {
|
|
48924
|
-
dateRanges: [
|
|
48932
|
+
dateRanges: [dateRange],
|
|
48925
48933
|
dimensions: [
|
|
48926
48934
|
{ name: GA4_DIMENSIONS.date },
|
|
48927
48935
|
{ name: GA4_DIMENSIONS.sessionSource },
|
|
@@ -48956,10 +48964,11 @@ async function fetchSocialReferrals(accessToken, propertyId, days) {
|
|
|
48956
48964
|
channelGroup: row.dimensionValues[3].value
|
|
48957
48965
|
}));
|
|
48958
48966
|
rows.push(...pageRows);
|
|
48959
|
-
const totalRows = response.rowCount
|
|
48967
|
+
const totalRows = response.rowCount;
|
|
48960
48968
|
offset += pageRows.length;
|
|
48961
48969
|
pageCount += 1;
|
|
48962
|
-
if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
|
|
48970
|
+
if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
|
|
48971
|
+
if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
|
|
48963
48972
|
}
|
|
48964
48973
|
for (const row of rows) {
|
|
48965
48974
|
row.date = compactDateToIso(row.date);
|
|
@@ -49012,10 +49021,14 @@ function isSearchConsoleUnavailable(error) {
|
|
|
49012
49021
|
}
|
|
49013
49022
|
async function fetchSearchLandingWindow(accessToken, propertyId, window2, options) {
|
|
49014
49023
|
const days = GA_SEARCH_LANDING_WINDOW_DAYS[window2];
|
|
49024
|
+
const explicitRange = options.timeZone === void 0 ? null : {
|
|
49025
|
+
startDate: isoDateDaysBeforeInTimeZone(options.requestedAt, days, options.timeZone),
|
|
49026
|
+
endDate: isoDateDaysBeforeInTimeZone(options.requestedAt, 1, options.timeZone)
|
|
49027
|
+
};
|
|
49015
49028
|
const { rows: rawRows, firstPage, reportRowCount, capped } = await fetchPagedReportWithTotals(accessToken, propertyId, {
|
|
49016
|
-
//
|
|
49017
|
-
//
|
|
49018
|
-
dateRanges: [{ startDate: `${days}daysAgo`, endDate: "yesterday" }],
|
|
49029
|
+
// A known reporting zone pins the planner's calendar bounds across I/O.
|
|
49030
|
+
// Otherwise GA4 resolves the relative range in its own reporting zone.
|
|
49031
|
+
dateRanges: [explicitRange ?? { startDate: `${days}daysAgo`, endDate: "yesterday" }],
|
|
49019
49032
|
dimensions: [{ name: GA4_DIMENSIONS.landingPagePlusQueryString }],
|
|
49020
49033
|
metrics: SEARCH_LANDING_METRIC_NAMES.map((name) => ({ name })),
|
|
49021
49034
|
metricAggregations: ["TOTAL"],
|
|
@@ -49047,8 +49060,8 @@ async function fetchSearchLandingWindow(accessToken, propertyId, window2, option
|
|
|
49047
49060
|
const zone = timeZone ?? "UTC";
|
|
49048
49061
|
return {
|
|
49049
49062
|
window: window2,
|
|
49050
|
-
periodStart: isoDateDaysBeforeInTimeZone(options.requestedAt, days, zone),
|
|
49051
|
-
periodEnd: isoDateDaysBeforeInTimeZone(options.requestedAt, 1, zone),
|
|
49063
|
+
periodStart: explicitRange?.startDate ?? isoDateDaysBeforeInTimeZone(options.requestedAt, days, zone),
|
|
49064
|
+
periodEnd: explicitRange?.endDate ?? isoDateDaysBeforeInTimeZone(options.requestedAt, 1, zone),
|
|
49052
49065
|
timeZone,
|
|
49053
49066
|
total,
|
|
49054
49067
|
rows,
|
|
@@ -49062,13 +49075,17 @@ async function fetchSearchLandingWindow(accessToken, propertyId, window2, option
|
|
|
49062
49075
|
async function fetchSearchLandingPages(accessToken, propertyId, options = {}) {
|
|
49063
49076
|
validateAccessToken2(accessToken);
|
|
49064
49077
|
validatePropertyId(propertyId);
|
|
49078
|
+
const requestedWindows = options.windows ?? GA_SEARCH_LANDING_WINDOWS;
|
|
49079
|
+
if (requestedWindows.length === 0 || new Set(requestedWindows).size !== requestedWindows.length || requestedWindows.some((window2) => !GA_SEARCH_LANDING_WINDOWS.includes(window2))) {
|
|
49080
|
+
throw new GA4ApiError("Search landing windows must be a non-empty selection of unique supported windows", 400);
|
|
49081
|
+
}
|
|
49065
49082
|
const requestedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
49066
49083
|
const pageSize = options.pageSize ?? GA4_SEARCH_LANDING_PAGE_SIZE;
|
|
49067
49084
|
const maxRows = options.maxRows ?? GA4_SEARCH_LANDING_MAX_ROWS;
|
|
49068
49085
|
ga4Log("info", "fetch-search-landing.start", { propertyId });
|
|
49069
49086
|
let windows;
|
|
49070
49087
|
try {
|
|
49071
|
-
windows = await Promise.all(
|
|
49088
|
+
windows = await Promise.all(requestedWindows.map((window2) => fetchSearchLandingWindow(accessToken, propertyId, window2, { requestedAt, pageSize, maxRows, timeZone: options.timeZone })));
|
|
49072
49089
|
} catch (error) {
|
|
49073
49090
|
if (!isSearchConsoleUnavailable(error)) throw error;
|
|
49074
49091
|
ga4Log("info", "fetch-search-landing.unavailable", { propertyId, httpStatus: 400 });
|
|
@@ -59608,7 +59625,7 @@ async function cdpRoutes(app, opts) {
|
|
|
59608
59625
|
|
|
59609
59626
|
// ../api-routes/src/ga.ts
|
|
59610
59627
|
import crypto51 from "crypto";
|
|
59611
|
-
import { eq as
|
|
59628
|
+
import { eq as eq86, desc as desc38, and as and74, sql as sql37 } from "drizzle-orm";
|
|
59612
59629
|
|
|
59613
59630
|
// ../api-routes/src/ga-session-history.ts
|
|
59614
59631
|
function buildSessionHistory(landingPageDays, dailyTotals) {
|
|
@@ -59628,7 +59645,7 @@ function buildSessionHistory(landingPageDays, dailyTotals) {
|
|
|
59628
59645
|
// ../api-routes/src/ga-overview.ts
|
|
59629
59646
|
function parseGaOverviewWindow(value) {
|
|
59630
59647
|
if (value === void 0 || value === "") return "30d";
|
|
59631
|
-
if (!isGaOverviewWindow(value)) throw validationError(`Invalid window "${value}": the GA4 overview supports 7d, 30d or
|
|
59648
|
+
if (!isGaOverviewWindow(value)) throw validationError(`Invalid window "${value}": the GA4 overview supports 7d, 30d, 90d or 180d.`);
|
|
59632
59649
|
return value;
|
|
59633
59650
|
}
|
|
59634
59651
|
var NO_VALUES = {
|
|
@@ -59771,13 +59788,93 @@ function findBiggestMover(current, prior) {
|
|
|
59771
59788
|
return mover;
|
|
59772
59789
|
}
|
|
59773
59790
|
|
|
59791
|
+
// ../api-routes/src/ga-sync-coverage.ts
|
|
59792
|
+
import { and as and72, eq as eq84, lt as lt17 } from "drizzle-orm";
|
|
59793
|
+
function planGaSyncCoverage(db, input, now) {
|
|
59794
|
+
const definition2 = input.definition ?? "v1";
|
|
59795
|
+
const refreshStart = shiftIsoCalendarDate(input.requested.endDate, -13);
|
|
59796
|
+
const reconcileAfter = new Date(Date.parse(now) - 30 * 864e5).toISOString();
|
|
59797
|
+
const covered = new Set(db.select().from(gaSyncCoverage).where(and72(
|
|
59798
|
+
eq84(gaSyncCoverage.projectId, input.projectId),
|
|
59799
|
+
eq84(gaSyncCoverage.propertyId, input.propertyId),
|
|
59800
|
+
eq84(gaSyncCoverage.component, input.component),
|
|
59801
|
+
eq84(gaSyncCoverage.definition, definition2)
|
|
59802
|
+
)).all().filter((row) => row.settled && row.syncedAt > reconcileAfter).map((row) => row.date));
|
|
59803
|
+
const missing = calendarDateRange(input.requested.startDate, input.requested.endDate).filter((date) => date >= refreshStart || !covered.has(date));
|
|
59804
|
+
return {
|
|
59805
|
+
...input,
|
|
59806
|
+
definition: definition2,
|
|
59807
|
+
refreshStart,
|
|
59808
|
+
ranges: groupIsoDateRanges(missing).map((range) => ({ startDate: range.start, endDate: range.end }))
|
|
59809
|
+
};
|
|
59810
|
+
}
|
|
59811
|
+
function saveGaSyncCoverage(tx, plan, ranges, syncedAt, syncRunId) {
|
|
59812
|
+
for (const range of ranges) {
|
|
59813
|
+
for (const date of calendarDateRange(range.startDate, range.endDate)) {
|
|
59814
|
+
const values = {
|
|
59815
|
+
projectId: plan.projectId,
|
|
59816
|
+
propertyId: plan.propertyId,
|
|
59817
|
+
component: plan.component,
|
|
59818
|
+
definition: plan.definition,
|
|
59819
|
+
date,
|
|
59820
|
+
settled: date < plan.refreshStart,
|
|
59821
|
+
syncedAt,
|
|
59822
|
+
syncRunId
|
|
59823
|
+
};
|
|
59824
|
+
tx.insert(gaSyncCoverage).values(values).onConflictDoUpdate({
|
|
59825
|
+
target: [gaSyncCoverage.projectId, gaSyncCoverage.component, gaSyncCoverage.date],
|
|
59826
|
+
set: values
|
|
59827
|
+
}).run();
|
|
59828
|
+
}
|
|
59829
|
+
}
|
|
59830
|
+
tx.delete(gaSyncCoverage).where(and72(
|
|
59831
|
+
eq84(gaSyncCoverage.projectId, plan.projectId),
|
|
59832
|
+
lt17(gaSyncCoverage.date, shiftIsoCalendarDate(plan.requested.endDate, -362))
|
|
59833
|
+
)).run();
|
|
59834
|
+
}
|
|
59835
|
+
async function fetchGaSyncRanges(plan, fetchRange) {
|
|
59836
|
+
const results = [];
|
|
59837
|
+
for (const range of plan.ranges) {
|
|
59838
|
+
results.push({ range, report: await fetchRange(range) });
|
|
59839
|
+
}
|
|
59840
|
+
return results;
|
|
59841
|
+
}
|
|
59842
|
+
|
|
59774
59843
|
// ../api-routes/src/ga-search-landing-pages.ts
|
|
59775
59844
|
import crypto50 from "crypto";
|
|
59776
|
-
import { and as
|
|
59845
|
+
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";
|
|
59846
|
+
function searchLandingWindowsToFetch(db, projectId, propertyId, timeZone, requestedAt) {
|
|
59847
|
+
const state = db.select().from(gaMeasurementSyncStates).where(eq85(gaMeasurementSyncStates.projectId, projectId)).get();
|
|
59848
|
+
if (!timeZone || state?.searchLandingStatus !== "ready") return [...GA_SEARCH_LANDING_WINDOWS];
|
|
59849
|
+
const stored = db.select().from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).all();
|
|
59850
|
+
const now = Date.parse(requestedAt);
|
|
59851
|
+
const end = isoDateDaysBeforeInTimeZone(requestedAt, 1, timeZone);
|
|
59852
|
+
return GA_SEARCH_LANDING_WINDOWS.filter((window2) => {
|
|
59853
|
+
const snapshot = stored.find((row) => row.windowKey === window2);
|
|
59854
|
+
if (!snapshot) return true;
|
|
59855
|
+
const age = now - Date.parse(snapshot.syncedAt);
|
|
59856
|
+
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);
|
|
59857
|
+
});
|
|
59858
|
+
}
|
|
59777
59859
|
function persistSearchLandingSnapshot(db, input) {
|
|
59778
59860
|
db.transaction((tx) => {
|
|
59779
|
-
|
|
59861
|
+
if (input.reuse.windows.length > 0) {
|
|
59862
|
+
const missing = searchLandingWindowsToFetch(tx, input.projectId, input.propertyId, input.reuse.timeZone, input.reuse.requestedAt);
|
|
59863
|
+
if (missing.some((window2) => input.reuse.windows.includes(window2))) {
|
|
59864
|
+
throw new Error("Search landing snapshots changed during sync; retry to refresh the missing windows");
|
|
59865
|
+
}
|
|
59866
|
+
}
|
|
59867
|
+
const otherProperty = tx.select({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, input.projectId)).all().some((row) => row.propertyId !== input.propertyId);
|
|
59868
|
+
if (otherProperty) deleteSearchLandingRows(tx, input.projectId);
|
|
59780
59869
|
for (const window2 of input.windows) {
|
|
59870
|
+
tx.delete(gaSearchLandingPages).where(and73(
|
|
59871
|
+
eq85(gaSearchLandingPages.projectId, input.projectId),
|
|
59872
|
+
eq85(gaSearchLandingPages.windowKey, window2.window)
|
|
59873
|
+
)).run();
|
|
59874
|
+
tx.delete(gaSearchLandingWindows).where(and73(
|
|
59875
|
+
eq85(gaSearchLandingWindows.projectId, input.projectId),
|
|
59876
|
+
eq85(gaSearchLandingWindows.windowKey, window2.window)
|
|
59877
|
+
)).run();
|
|
59781
59878
|
tx.insert(gaSearchLandingWindows).values({
|
|
59782
59879
|
id: crypto50.randomUUID(),
|
|
59783
59880
|
projectId: input.projectId,
|
|
@@ -59786,6 +59883,7 @@ function persistSearchLandingSnapshot(db, input) {
|
|
|
59786
59883
|
periodStart: window2.periodStart,
|
|
59787
59884
|
periodEnd: window2.periodEnd,
|
|
59788
59885
|
timeZone: window2.timeZone,
|
|
59886
|
+
reportDefinition: GA4_SEARCH_LANDING_REPORT_DEFINITION,
|
|
59789
59887
|
totalClicks: window2.total.clicks,
|
|
59790
59888
|
totalImpressions: window2.total.impressions,
|
|
59791
59889
|
totalCtr: window2.total.ctr,
|
|
@@ -59819,7 +59917,7 @@ function persistSearchLandingSnapshot(db, input) {
|
|
|
59819
59917
|
upsertSearchLandingState(tx, input.projectId, {
|
|
59820
59918
|
searchLandingStatus: "ready",
|
|
59821
59919
|
searchLandingError: null,
|
|
59822
|
-
searchLandingSyncedAt: input.syncedAt,
|
|
59920
|
+
...input.windows.length > 0 ? { searchLandingSyncedAt: input.syncedAt } : {},
|
|
59823
59921
|
searchLandingAttemptedAt: input.syncedAt
|
|
59824
59922
|
}, input.syncedAt);
|
|
59825
59923
|
});
|
|
@@ -59837,7 +59935,7 @@ function persistSearchLandingSnapshot(db, input) {
|
|
|
59837
59935
|
}
|
|
59838
59936
|
function persistSearchLandingFailure(db, projectId, propertyId, status, message, attemptedAt) {
|
|
59839
59937
|
db.transaction((tx) => {
|
|
59840
|
-
const storedProperties = tx.selectDistinct({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(
|
|
59938
|
+
const storedProperties = tx.selectDistinct({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).all();
|
|
59841
59939
|
const otherProperty = storedProperties.some((stored) => stored.propertyId !== propertyId);
|
|
59842
59940
|
if (otherProperty) deleteSearchLandingRows(tx, projectId);
|
|
59843
59941
|
upsertSearchLandingState(tx, projectId, {
|
|
@@ -59849,8 +59947,8 @@ function persistSearchLandingFailure(db, projectId, propertyId, status, message,
|
|
|
59849
59947
|
});
|
|
59850
59948
|
}
|
|
59851
59949
|
function deleteSearchLandingRows(db, projectId) {
|
|
59852
|
-
db.delete(gaSearchLandingPages).where(
|
|
59853
|
-
db.delete(gaSearchLandingWindows).where(
|
|
59950
|
+
db.delete(gaSearchLandingPages).where(eq85(gaSearchLandingPages.projectId, projectId)).run();
|
|
59951
|
+
db.delete(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).run();
|
|
59854
59952
|
}
|
|
59855
59953
|
function upsertSearchLandingState(db, projectId, fields, updatedAt) {
|
|
59856
59954
|
db.insert(gaMeasurementSyncStates).values({ projectId, ...fields, updatedAt }).onConflictDoUpdate({
|
|
@@ -59882,11 +59980,11 @@ function buildGaSearchLandingPages(db, projectName, query2, connections) {
|
|
|
59882
59980
|
throw validationError('"offset" must be a non-negative integer');
|
|
59883
59981
|
}
|
|
59884
59982
|
const project = resolveProject(db, projectName);
|
|
59885
|
-
const storedWindow = db.select().from(gaSearchLandingWindows).where(
|
|
59983
|
+
const storedWindow = db.select().from(gaSearchLandingWindows).where(and73(eq85(gaSearchLandingWindows.projectId, project.id), eq85(gaSearchLandingWindows.windowKey, window2))).get();
|
|
59886
59984
|
const otherProperty = storedWindow !== void 0 && storedWindow.propertyId !== resolveCurrentGa4PropertyId(connections, project.name, project.canonicalDomain);
|
|
59887
59985
|
const stored = otherProperty ? void 0 : storedWindow;
|
|
59888
|
-
const state = otherProperty ? void 0 : db.select().from(gaMeasurementSyncStates).where(
|
|
59889
|
-
const pageScope =
|
|
59986
|
+
const state = otherProperty ? void 0 : db.select().from(gaMeasurementSyncStates).where(eq85(gaMeasurementSyncStates.projectId, project.id)).get();
|
|
59987
|
+
const pageScope = and73(eq85(gaSearchLandingPages.projectId, project.id), eq85(gaSearchLandingPages.windowKey, window2));
|
|
59890
59988
|
const totalRows = otherProperty ? 0 : db.select({ value: count5() }).from(gaSearchLandingPages).where(pageScope).get()?.value ?? 0;
|
|
59891
59989
|
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
59990
|
const total = stored ? toWireMetrics({
|
|
@@ -59926,7 +60024,7 @@ function searchConsoleLinkFor(db, projectId, total, windowStart, windowEnd) {
|
|
|
59926
60024
|
const stored = db.select({
|
|
59927
60025
|
days: count5(),
|
|
59928
60026
|
impressions: sql36`sum(${gscDailyTotals.impressions})`
|
|
59929
|
-
}).from(gscDailyTotals).where(
|
|
60027
|
+
}).from(gscDailyTotals).where(and73(eq85(gscDailyTotals.projectId, projectId), gte23(gscDailyTotals.date, windowStart), lte23(gscDailyTotals.date, windowEnd))).get();
|
|
59930
60028
|
const searchConsoleImpressions = stored && stored.days > 0 ? Number(stored.impressions ?? 0) : null;
|
|
59931
60029
|
if (gaSearchLandingHasSearchData(total)) return { state: "has-data", searchConsoleImpressions };
|
|
59932
60030
|
if (searchConsoleImpressions === null) return { state: "unknown", searchConsoleImpressions };
|
|
@@ -60025,8 +60123,8 @@ function keepWinningDimensionRows(rows, groupKey) {
|
|
|
60025
60123
|
function persistAcquisitionMeasurement(db, input) {
|
|
60026
60124
|
const { startDate, endDate } = input.report;
|
|
60027
60125
|
db.transaction((tx) => {
|
|
60028
|
-
tx.delete(gaAcquisitionDaily).where(
|
|
60029
|
-
|
|
60126
|
+
tx.delete(gaAcquisitionDaily).where(and74(
|
|
60127
|
+
eq86(gaAcquisitionDaily.projectId, input.projectId),
|
|
60030
60128
|
sql37`${gaAcquisitionDaily.date} >= ${startDate}`,
|
|
60031
60129
|
sql37`${gaAcquisitionDaily.date} <= ${endDate}`
|
|
60032
60130
|
)).run();
|
|
@@ -60067,8 +60165,8 @@ function persistAcquisitionMeasurement(db, input) {
|
|
|
60067
60165
|
function persistLeadMeasurement(db, input) {
|
|
60068
60166
|
const { startDate, endDate } = input.report;
|
|
60069
60167
|
db.transaction((tx) => {
|
|
60070
|
-
tx.delete(gaLeadEventsDaily).where(
|
|
60071
|
-
|
|
60168
|
+
tx.delete(gaLeadEventsDaily).where(and74(
|
|
60169
|
+
eq86(gaLeadEventsDaily.projectId, input.projectId),
|
|
60072
60170
|
sql37`${gaLeadEventsDaily.date} >= ${startDate}`,
|
|
60073
60171
|
sql37`${gaLeadEventsDaily.date} <= ${endDate}`
|
|
60074
60172
|
)).run();
|
|
@@ -60114,7 +60212,8 @@ function persistLeadMeasurement(db, input) {
|
|
|
60114
60212
|
}
|
|
60115
60213
|
function resetLeadMeasurement(db, projectId, updatedAt) {
|
|
60116
60214
|
db.transaction((tx) => {
|
|
60117
|
-
tx.delete(gaLeadEventsDaily).where(
|
|
60215
|
+
tx.delete(gaLeadEventsDaily).where(eq86(gaLeadEventsDaily.projectId, projectId)).run();
|
|
60216
|
+
tx.delete(gaSyncCoverage).where(and74(eq86(gaSyncCoverage.projectId, projectId), eq86(gaSyncCoverage.component, "leads"))).run();
|
|
60118
60217
|
tx.insert(gaMeasurementSyncStates).values({ projectId, leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }).onConflictDoUpdate({
|
|
60119
60218
|
target: gaMeasurementSyncStates.projectId,
|
|
60120
60219
|
set: { leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }
|
|
@@ -60456,7 +60555,7 @@ async function ga4Routes(app, opts) {
|
|
|
60456
60555
|
if (!connected2) {
|
|
60457
60556
|
return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
|
|
60458
60557
|
}
|
|
60459
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
60558
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
60460
60559
|
return {
|
|
60461
60560
|
connected: true,
|
|
60462
60561
|
propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
|
|
@@ -60479,7 +60578,7 @@ async function ga4Routes(app, opts) {
|
|
|
60479
60578
|
const syncAi = !only || only === "ai";
|
|
60480
60579
|
const syncSocial = !only || only === "social";
|
|
60481
60580
|
const syncSearchLanding = !only || only === "search-landing";
|
|
60482
|
-
const measurementState = app.db.select().from(gaMeasurementSyncStates).where(
|
|
60581
|
+
const measurementState = app.db.select().from(gaMeasurementSyncStates).where(eq86(gaMeasurementSyncStates.projectId, project.id)).get();
|
|
60483
60582
|
const acquisitionDays = measurementState?.acquisitionSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
|
|
60484
60583
|
let leadDays = GA4_MAX_SYNC_DAYS;
|
|
60485
60584
|
let leadEventNames = [];
|
|
@@ -60516,47 +60615,72 @@ async function ga4Routes(app, opts) {
|
|
|
60516
60615
|
let rows = [];
|
|
60517
60616
|
let aiReferrals = [];
|
|
60518
60617
|
let socialReferrals = [];
|
|
60519
|
-
const WINDOW_KEYS =
|
|
60520
|
-
const
|
|
60618
|
+
const WINDOW_KEYS = GA_OVERVIEW_WINDOWS;
|
|
60619
|
+
const [summary3, windowSummaries, windowMetrics] = await Promise.all([
|
|
60521
60620
|
fetchAggregateSummary(accessToken, propertyId, days),
|
|
60522
|
-
|
|
60523
|
-
|
|
60524
|
-
|
|
60525
|
-
|
|
60526
|
-
|
|
60527
|
-
|
|
60528
|
-
|
|
60621
|
+
Promise.all(WINDOW_KEYS.map((w) => fetchWindowSummary(accessToken, propertyId, w))),
|
|
60622
|
+
Promise.all(WINDOW_KEYS.map((w) => fetchWindowMetrics(accessToken, propertyId, w)))
|
|
60623
|
+
]);
|
|
60624
|
+
const requested = { startDate: summary3.periodStart, endDate: summary3.periodEnd };
|
|
60625
|
+
const definition2 = JSON.stringify(["v1", windowMetrics[0].timeZone ?? "UTC"]);
|
|
60626
|
+
const planInput = { projectId: project.id, propertyId, requested, definition: definition2 };
|
|
60627
|
+
const trafficPlan = planGaSyncCoverage(app.db, { ...planInput, component: "traffic" }, startedAt);
|
|
60628
|
+
const aiPlan = planGaSyncCoverage(app.db, { ...planInput, component: "ai" }, startedAt);
|
|
60629
|
+
const socialPlan = planGaSyncCoverage(app.db, { ...planInput, component: "social" }, startedAt);
|
|
60630
|
+
const longestWindow = windowMetrics.at(-1);
|
|
60631
|
+
const dailyPlan = planGaSyncCoverage(app.db, {
|
|
60632
|
+
...planInput,
|
|
60633
|
+
component: "daily",
|
|
60634
|
+
requested: { startDate: longestWindow.previous.startDate, endDate: shiftIsoCalendarDate(longestWindow.current.endDate, 1) }
|
|
60635
|
+
}, startedAt);
|
|
60636
|
+
const acquisitionPlan = planGaSyncCoverage(app.db, {
|
|
60637
|
+
...planInput,
|
|
60638
|
+
component: "acquisition",
|
|
60639
|
+
requested: { startDate: shiftIsoCalendarDate(requested.endDate, -acquisitionDays), endDate: requested.endDate }
|
|
60640
|
+
}, startedAt);
|
|
60641
|
+
const leadPlan = planGaSyncCoverage(app.db, {
|
|
60642
|
+
...planInput,
|
|
60643
|
+
component: "leads",
|
|
60644
|
+
definition: JSON.stringify([definition2, ...leadEventNames]),
|
|
60645
|
+
requested: { startDate: shiftIsoCalendarDate(requested.endDate, -leadDays), endDate: requested.endDate }
|
|
60646
|
+
}, startedAt);
|
|
60647
|
+
const searchRequestedAt = /* @__PURE__ */ new Date();
|
|
60648
|
+
const searchTimeZone = windowMetrics[0].timeZone ?? null;
|
|
60649
|
+
const searchWindows = syncSearchLanding ? searchLandingWindowsToFetch(app.db, project.id, propertyId, searchTimeZone, searchRequestedAt.toISOString()) : [];
|
|
60529
60650
|
const measurementFetch = Promise.allSettled([
|
|
60530
|
-
fetchAcquisitionByChannel(accessToken, propertyId, acquisitionDays),
|
|
60531
|
-
leadEventNames.length > 0 ? fetchLeadEvents(accessToken, propertyId, leadEventNames, leadDays) : Promise.resolve(null),
|
|
60532
|
-
|
|
60533
|
-
|
|
60651
|
+
fetchGaSyncRanges(acquisitionPlan, (range) => fetchAcquisitionByChannel(accessToken, propertyId, acquisitionDays, { dateRange: range })),
|
|
60652
|
+
leadEventNames.length > 0 ? fetchGaSyncRanges(leadPlan, (range) => fetchLeadEvents(accessToken, propertyId, leadEventNames, leadDays, range)) : Promise.resolve(null),
|
|
60653
|
+
syncSearchLanding ? searchWindows.length > 0 ? fetchSearchLandingPages(accessToken, propertyId, {
|
|
60654
|
+
windows: searchWindows,
|
|
60655
|
+
now: searchRequestedAt,
|
|
60656
|
+
timeZone: searchTimeZone ?? void 0
|
|
60657
|
+
}) : Promise.resolve({ status: "ready", windows: [] }) : Promise.resolve(null)
|
|
60534
60658
|
]);
|
|
60535
|
-
const
|
|
60536
|
-
|
|
60537
|
-
|
|
60538
|
-
|
|
60539
|
-
|
|
60540
|
-
|
|
60541
|
-
|
|
60542
|
-
|
|
60543
|
-
|
|
60544
|
-
|
|
60545
|
-
aiReferrals = results[idx++];
|
|
60546
|
-
}
|
|
60547
|
-
if (syncSocial) {
|
|
60548
|
-
socialReferrals = results[idx++];
|
|
60549
|
-
}
|
|
60659
|
+
const [dailyReports, trafficReports, aiReports, socialReports] = await Promise.all([
|
|
60660
|
+
fetchGaSyncRanges(dailyPlan, (range) => fetchDailyTotals(accessToken, propertyId, void 0, { dateRange: range })),
|
|
60661
|
+
fetchGaSyncRanges(trafficPlan, (range) => fetchTrafficByLandingPage(accessToken, propertyId, days, range)),
|
|
60662
|
+
syncAi ? fetchGaSyncRanges(aiPlan, (range) => fetchAiReferrals(accessToken, propertyId, days, range)) : Promise.resolve([]),
|
|
60663
|
+
syncSocial ? fetchGaSyncRanges(socialPlan, (range) => fetchSocialReferrals(accessToken, propertyId, days, range)) : Promise.resolve([])
|
|
60664
|
+
]);
|
|
60665
|
+
rows = trafficReports.flatMap((result) => result.report);
|
|
60666
|
+
aiReferrals = aiReports.flatMap((result) => result.report);
|
|
60667
|
+
socialReferrals = socialReports.flatMap((result) => result.report);
|
|
60668
|
+
const dailyTotals = { rows: dailyReports.flatMap((result) => result.report.rows) };
|
|
60550
60669
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60670
|
+
if (resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain) !== propertyId) {
|
|
60671
|
+
throw validationError("GA4 property changed during sync; retry against the current connection");
|
|
60672
|
+
}
|
|
60551
60673
|
app.db.transaction((tx) => {
|
|
60552
60674
|
if (syncTraffic) {
|
|
60553
|
-
|
|
60554
|
-
|
|
60555
|
-
|
|
60556
|
-
|
|
60557
|
-
|
|
60558
|
-
|
|
60559
|
-
|
|
60675
|
+
for (const { range } of trafficReports) {
|
|
60676
|
+
tx.delete(gaTrafficSnapshots).where(
|
|
60677
|
+
and74(
|
|
60678
|
+
eq86(gaTrafficSnapshots.projectId, project.id),
|
|
60679
|
+
sql37`${gaTrafficSnapshots.date} >= ${range.startDate}`,
|
|
60680
|
+
sql37`${gaTrafficSnapshots.date} <= ${range.endDate}`
|
|
60681
|
+
)
|
|
60682
|
+
).run();
|
|
60683
|
+
}
|
|
60560
60684
|
for (const row of rows) {
|
|
60561
60685
|
tx.insert(gaTrafficSnapshots).values({
|
|
60562
60686
|
id: crypto51.randomUUID(),
|
|
@@ -60573,13 +60697,15 @@ async function ga4Routes(app, opts) {
|
|
|
60573
60697
|
}).run();
|
|
60574
60698
|
}
|
|
60575
60699
|
}
|
|
60576
|
-
|
|
60577
|
-
|
|
60578
|
-
|
|
60579
|
-
|
|
60580
|
-
|
|
60581
|
-
|
|
60582
|
-
|
|
60700
|
+
for (const { report } of dailyReports) {
|
|
60701
|
+
tx.delete(gaDailyTotals).where(
|
|
60702
|
+
and74(
|
|
60703
|
+
eq86(gaDailyTotals.projectId, project.id),
|
|
60704
|
+
sql37`${gaDailyTotals.date} >= ${report.startDate}`,
|
|
60705
|
+
sql37`${gaDailyTotals.date} <= ${report.endDate}`
|
|
60706
|
+
)
|
|
60707
|
+
).run();
|
|
60708
|
+
}
|
|
60583
60709
|
for (const row of dailyTotals.rows) {
|
|
60584
60710
|
tx.insert(gaDailyTotals).values({
|
|
60585
60711
|
id: crypto51.randomUUID(),
|
|
@@ -60605,7 +60731,7 @@ async function ga4Routes(app, opts) {
|
|
|
60605
60731
|
createdAt: now
|
|
60606
60732
|
}).run();
|
|
60607
60733
|
}
|
|
60608
|
-
tx.delete(gaWindowMetrics).where(
|
|
60734
|
+
tx.delete(gaWindowMetrics).where(eq86(gaWindowMetrics.projectId, project.id)).run();
|
|
60609
60735
|
for (const windowMetric of windowMetrics) {
|
|
60610
60736
|
for (const [period2, totals] of [
|
|
60611
60737
|
[GaOverviewPeriods.current, windowMetric.current],
|
|
@@ -60635,13 +60761,15 @@ async function ga4Routes(app, opts) {
|
|
|
60635
60761
|
}
|
|
60636
60762
|
}
|
|
60637
60763
|
if (syncAi) {
|
|
60638
|
-
|
|
60639
|
-
|
|
60640
|
-
|
|
60641
|
-
|
|
60642
|
-
|
|
60643
|
-
|
|
60644
|
-
|
|
60764
|
+
for (const { range } of aiReports) {
|
|
60765
|
+
tx.delete(gaAiReferrals).where(
|
|
60766
|
+
and74(
|
|
60767
|
+
eq86(gaAiReferrals.projectId, project.id),
|
|
60768
|
+
sql37`${gaAiReferrals.date} >= ${range.startDate}`,
|
|
60769
|
+
sql37`${gaAiReferrals.date} <= ${range.endDate}`
|
|
60770
|
+
)
|
|
60771
|
+
).run();
|
|
60772
|
+
}
|
|
60645
60773
|
for (const row of aiReferrals) {
|
|
60646
60774
|
tx.insert(gaAiReferrals).values({
|
|
60647
60775
|
id: crypto51.randomUUID(),
|
|
@@ -60667,13 +60795,15 @@ async function ga4Routes(app, opts) {
|
|
|
60667
60795
|
}
|
|
60668
60796
|
}
|
|
60669
60797
|
if (syncSocial) {
|
|
60670
|
-
|
|
60671
|
-
|
|
60672
|
-
|
|
60673
|
-
|
|
60674
|
-
|
|
60675
|
-
|
|
60676
|
-
|
|
60798
|
+
for (const { range } of socialReports) {
|
|
60799
|
+
tx.delete(gaSocialReferrals).where(
|
|
60800
|
+
and74(
|
|
60801
|
+
eq86(gaSocialReferrals.projectId, project.id),
|
|
60802
|
+
sql37`${gaSocialReferrals.date} >= ${range.startDate}`,
|
|
60803
|
+
sql37`${gaSocialReferrals.date} <= ${range.endDate}`
|
|
60804
|
+
)
|
|
60805
|
+
).run();
|
|
60806
|
+
}
|
|
60677
60807
|
for (const row of socialReferrals) {
|
|
60678
60808
|
tx.insert(gaSocialReferrals).values({
|
|
60679
60809
|
id: crypto51.randomUUID(),
|
|
@@ -60690,7 +60820,7 @@ async function ga4Routes(app, opts) {
|
|
|
60690
60820
|
}
|
|
60691
60821
|
}
|
|
60692
60822
|
if (syncSummary) {
|
|
60693
|
-
tx.delete(gaTrafficSummaries).where(
|
|
60823
|
+
tx.delete(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).run();
|
|
60694
60824
|
tx.insert(gaTrafficSummaries).values({
|
|
60695
60825
|
id: crypto51.randomUUID(),
|
|
60696
60826
|
projectId: project.id,
|
|
@@ -60702,7 +60832,7 @@ async function ga4Routes(app, opts) {
|
|
|
60702
60832
|
syncedAt: now,
|
|
60703
60833
|
syncRunId: runId
|
|
60704
60834
|
}).run();
|
|
60705
|
-
tx.delete(gaTrafficWindowSummaries).where(
|
|
60835
|
+
tx.delete(gaTrafficWindowSummaries).where(eq86(gaTrafficWindowSummaries.projectId, project.id)).run();
|
|
60706
60836
|
for (const ws of windowSummaries) {
|
|
60707
60837
|
tx.insert(gaTrafficWindowSummaries).values({
|
|
60708
60838
|
id: crypto51.randomUUID(),
|
|
@@ -60719,9 +60849,16 @@ async function ga4Routes(app, opts) {
|
|
|
60719
60849
|
}).run();
|
|
60720
60850
|
}
|
|
60721
60851
|
}
|
|
60852
|
+
saveGaSyncCoverage(tx, trafficPlan, trafficReports.map((result) => result.range), now, runId);
|
|
60853
|
+
saveGaSyncCoverage(tx, dailyPlan, dailyReports.map((result) => result.report), now, runId);
|
|
60854
|
+
if (syncAi) saveGaSyncCoverage(tx, aiPlan, aiReports.map((result) => result.range), now, runId);
|
|
60855
|
+
if (syncSocial) saveGaSyncCoverage(tx, socialPlan, socialReports.map((result) => result.range), now, runId);
|
|
60722
60856
|
});
|
|
60723
60857
|
const [acquisitionSettled, leadsSettled, searchLandingSettled] = await measurementFetch;
|
|
60724
60858
|
const measurementNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
60859
|
+
if (resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain) !== propertyId) {
|
|
60860
|
+
throw validationError("GA4 property changed during sync; retry against the current connection");
|
|
60861
|
+
}
|
|
60725
60862
|
const recordMeasurementFailure = (component, reason) => {
|
|
60726
60863
|
componentFailure ??= { error: reason };
|
|
60727
60864
|
const message = describeError(reason);
|
|
@@ -60748,16 +60885,21 @@ async function ga4Routes(app, opts) {
|
|
|
60748
60885
|
return recordMeasurementFailure("acquisition", acquisitionSettled.reason);
|
|
60749
60886
|
}
|
|
60750
60887
|
try {
|
|
60751
|
-
|
|
60752
|
-
|
|
60753
|
-
|
|
60754
|
-
|
|
60755
|
-
|
|
60888
|
+
app.db.transaction((tx) => {
|
|
60889
|
+
for (const { report } of acquisitionSettled.value) {
|
|
60890
|
+
persistAcquisitionMeasurement(tx, {
|
|
60891
|
+
projectId: project.id,
|
|
60892
|
+
runId,
|
|
60893
|
+
syncedAt: measurementNow,
|
|
60894
|
+
report
|
|
60895
|
+
});
|
|
60896
|
+
}
|
|
60897
|
+
saveGaSyncCoverage(tx, acquisitionPlan, acquisitionSettled.value.map((result) => result.report), measurementNow, runId);
|
|
60756
60898
|
});
|
|
60757
60899
|
return {
|
|
60758
60900
|
status: "ready",
|
|
60759
60901
|
days: acquisitionDays,
|
|
60760
|
-
rowCount: acquisitionSettled.value.rows.length
|
|
60902
|
+
rowCount: acquisitionSettled.value.reduce((sum, result) => sum + result.report.rows.length, 0)
|
|
60761
60903
|
};
|
|
60762
60904
|
} catch (error) {
|
|
60763
60905
|
return recordMeasurementFailure("acquisition", error);
|
|
@@ -60772,18 +60914,23 @@ async function ga4Routes(app, opts) {
|
|
|
60772
60914
|
return { status: "not-configured", days: 0, rowCount: 0 };
|
|
60773
60915
|
}
|
|
60774
60916
|
try {
|
|
60775
|
-
|
|
60776
|
-
|
|
60777
|
-
|
|
60778
|
-
|
|
60779
|
-
|
|
60780
|
-
|
|
60917
|
+
app.db.transaction((tx) => {
|
|
60918
|
+
for (const { report } of leadsSettled.value) {
|
|
60919
|
+
persistLeadMeasurement(tx, {
|
|
60920
|
+
projectId: project.id,
|
|
60921
|
+
runId,
|
|
60922
|
+
syncedAt: measurementNow,
|
|
60923
|
+
report,
|
|
60924
|
+
eventNames: leadEventNames
|
|
60925
|
+
});
|
|
60926
|
+
}
|
|
60927
|
+
saveGaSyncCoverage(tx, leadPlan, leadsSettled.value.map((result) => result.report), measurementNow, runId);
|
|
60781
60928
|
});
|
|
60782
60929
|
return {
|
|
60783
60930
|
status: "ready",
|
|
60784
60931
|
days: leadDays,
|
|
60785
|
-
rowCount: leadsSettled.value.rows.length,
|
|
60786
|
-
attributionScope: leadsSettled.value.attributionScope,
|
|
60932
|
+
rowCount: leadsSettled.value.reduce((sum, result) => sum + result.report.rows.length, 0),
|
|
60933
|
+
attributionScope: leadsSettled.value.at(-1).report.attributionScope,
|
|
60787
60934
|
eventNames: leadEventNames,
|
|
60788
60935
|
keyEvents: keyEventRead
|
|
60789
60936
|
};
|
|
@@ -60826,19 +60973,24 @@ async function ga4Routes(app, opts) {
|
|
|
60826
60973
|
propertyId,
|
|
60827
60974
|
runId,
|
|
60828
60975
|
syncedAt: measurementNow,
|
|
60829
|
-
windows: report.windows
|
|
60976
|
+
windows: report.windows,
|
|
60977
|
+
reuse: {
|
|
60978
|
+
windows: GA_SEARCH_LANDING_WINDOWS.filter((window2) => !searchWindows.includes(window2)),
|
|
60979
|
+
timeZone: searchTimeZone,
|
|
60980
|
+
requestedAt: searchRequestedAt.toISOString()
|
|
60981
|
+
}
|
|
60830
60982
|
});
|
|
60831
60983
|
} catch (error) {
|
|
60832
60984
|
return recordSearchLandingFailure("error", error);
|
|
60833
60985
|
}
|
|
60834
60986
|
})();
|
|
60835
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: measurementNow }).where(
|
|
60987
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: measurementNow }).where(eq86(runs.id, runId)).run();
|
|
60836
60988
|
const syncedComponents = only ? [
|
|
60837
60989
|
...syncTraffic ? ["traffic"] : [],
|
|
60838
60990
|
...syncSummary ? ["summary"] : [],
|
|
60839
60991
|
...syncAi ? ["ai"] : [],
|
|
60840
60992
|
...syncSocial ? ["social"] : [],
|
|
60841
|
-
...searchLandingResult?.status === "ready" ? ["search-landing"] : []
|
|
60993
|
+
...searchLandingResult?.status === "ready" && searchLandingResult.windows.length > 0 ? ["search-landing"] : []
|
|
60842
60994
|
] : void 0;
|
|
60843
60995
|
gaLog("info", "sync.complete", {
|
|
60844
60996
|
projectId: project.id,
|
|
@@ -60878,7 +61030,7 @@ async function ga4Routes(app, opts) {
|
|
|
60878
61030
|
} catch (e) {
|
|
60879
61031
|
const msg = describeError(e);
|
|
60880
61032
|
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(
|
|
61033
|
+
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq86(runs.id, runId)).run();
|
|
60882
61034
|
outcome2.report({ status: OutcomeStatuses.failed, ...ga4SyncFailure(e, credentialsResolved, opts, project) });
|
|
60883
61035
|
throw e;
|
|
60884
61036
|
}
|
|
@@ -60887,7 +61039,7 @@ async function ga4Routes(app, opts) {
|
|
|
60887
61039
|
const project = resolveProject(app.db, request.params.name);
|
|
60888
61040
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
60889
61041
|
const limit = Math.max(1, Math.min(parseInt(request.query.limit ?? "50", 10) || 50, 500));
|
|
60890
|
-
const range = resolveDateRange(request.query);
|
|
61042
|
+
const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
|
|
60891
61043
|
const windowSummaryRow = range.startDate && !range.explicitDates ? app.db.select({
|
|
60892
61044
|
periodStart: gaTrafficWindowSummaries.periodStart,
|
|
60893
61045
|
periodEnd: gaTrafficWindowSummaries.periodEnd,
|
|
@@ -60896,9 +61048,9 @@ async function ga4Routes(app, opts) {
|
|
|
60896
61048
|
totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
|
|
60897
61049
|
totalUsers: gaTrafficWindowSummaries.totalUsers
|
|
60898
61050
|
}).from(gaTrafficWindowSummaries).where(
|
|
60899
|
-
|
|
60900
|
-
|
|
60901
|
-
|
|
61051
|
+
and74(
|
|
61052
|
+
eq86(gaTrafficWindowSummaries.projectId, project.id),
|
|
61053
|
+
eq86(gaTrafficWindowSummaries.windowKey, range.window)
|
|
60902
61054
|
)
|
|
60903
61055
|
).get() : null;
|
|
60904
61056
|
const projectSummaryRow = app.db.select({
|
|
@@ -60907,11 +61059,11 @@ async function ga4Routes(app, opts) {
|
|
|
60907
61059
|
totalSessions: gaTrafficSummaries.totalSessions,
|
|
60908
61060
|
totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
|
|
60909
61061
|
totalUsers: gaTrafficSummaries.totalUsers
|
|
60910
|
-
}).from(gaTrafficSummaries).where(
|
|
61062
|
+
}).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).get();
|
|
60911
61063
|
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(
|
|
61064
|
+
app.db.select({ min: sql37`MIN(${gaTrafficSnapshots.date})`, max: sql37`MAX(${gaTrafficSnapshots.date})` }).from(gaTrafficSnapshots).where(eq86(gaTrafficSnapshots.projectId, project.id)).get(),
|
|
61065
|
+
app.db.select({ min: sql37`MIN(${gaAiReferrals.date})`, max: sql37`MAX(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq86(gaAiReferrals.projectId, project.id)).get(),
|
|
61066
|
+
app.db.select({ min: sql37`MIN(${gaSocialReferrals.date})`, max: sql37`MAX(${gaSocialReferrals.date})` }).from(gaSocialReferrals).where(eq86(gaSocialReferrals.projectId, project.id)).get()
|
|
60915
61067
|
];
|
|
60916
61068
|
const retainedDates = retainedBounds.flatMap((bound) => [bound?.min, bound?.max]).filter((date) => Boolean(date));
|
|
60917
61069
|
const projectSummaryCoversAll = Boolean(
|
|
@@ -60920,15 +61072,15 @@ async function ga4Routes(app, opts) {
|
|
|
60920
61072
|
const denominatorRow = windowSummaryRow ?? (range.window === "all" && projectSummaryCoversAll ? projectSummaryRow : null);
|
|
60921
61073
|
const denominatorPeriod = denominatorRow?.periodStart && denominatorRow.periodEnd ? { startDate: denominatorRow.periodStart, endDate: denominatorRow.periodEnd } : null;
|
|
60922
61074
|
const measuredRange = denominatorPeriod ? { ...range, ...denominatorPeriod } : range;
|
|
60923
|
-
const snapshotConditions = [
|
|
60924
|
-
const aiConditions = [
|
|
60925
|
-
const socialConditions = [
|
|
61075
|
+
const snapshotConditions = [eq86(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, measuredRange)];
|
|
61076
|
+
const aiConditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, measuredRange)];
|
|
61077
|
+
const socialConditions = [eq86(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, measuredRange)];
|
|
60926
61078
|
const snapshotTotalsRow = !denominatorRow ? (() => {
|
|
60927
61079
|
const summed = app.db.select({
|
|
60928
61080
|
totalSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
|
|
60929
61081
|
totalOrganicSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
|
|
60930
61082
|
totalUsers: sql37`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
|
|
60931
|
-
}).from(gaTrafficSnapshots).where(
|
|
61083
|
+
}).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).get();
|
|
60932
61084
|
return {
|
|
60933
61085
|
totalSessions: summed?.totalSessions ?? 0,
|
|
60934
61086
|
totalOrganicSessions: summed?.totalOrganicSessions ?? 0,
|
|
@@ -60941,21 +61093,21 @@ async function ga4Routes(app, opts) {
|
|
|
60941
61093
|
const summaryRow = denominatorRow ?? snapshotTotalsRow;
|
|
60942
61094
|
const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
|
|
60943
61095
|
totalDirectSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
|
|
60944
|
-
}).from(gaTrafficSnapshots).where(
|
|
61096
|
+
}).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).get();
|
|
60945
61097
|
const rows = app.db.select({
|
|
60946
61098
|
landingPage: sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
|
|
60947
61099
|
sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
|
|
60948
61100
|
organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
60949
61101
|
directSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
|
|
60950
61102
|
users: sql37`SUM(${gaTrafficSnapshots.users})`
|
|
60951
|
-
}).from(gaTrafficSnapshots).where(
|
|
61103
|
+
}).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
|
|
60952
61104
|
const aiReferralRows = app.db.select({
|
|
60953
61105
|
source: gaAiReferrals.source,
|
|
60954
61106
|
medium: gaAiReferrals.medium,
|
|
60955
61107
|
trafficClass: gaAiReferrals.trafficClass,
|
|
60956
61108
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
60957
61109
|
sessions: sql37`SUM(${gaAiReferrals.sessions})`
|
|
60958
|
-
}).from(gaAiReferrals).where(
|
|
61110
|
+
}).from(gaAiReferrals).where(and74(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.trafficClass, gaAiReferrals.sourceDimension).all();
|
|
60959
61111
|
const aiReferralLandingPageRows = app.db.select({
|
|
60960
61112
|
source: gaAiReferrals.source,
|
|
60961
61113
|
medium: gaAiReferrals.medium,
|
|
@@ -60963,7 +61115,7 @@ async function ga4Routes(app, opts) {
|
|
|
60963
61115
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
60964
61116
|
landingPage: sql37`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
60965
61117
|
sessions: sql37`SUM(${gaAiReferrals.sessions})`
|
|
60966
|
-
}).from(gaAiReferrals).where(
|
|
61118
|
+
}).from(gaAiReferrals).where(and74(...aiConditions)).groupBy(
|
|
60967
61119
|
gaAiReferrals.source,
|
|
60968
61120
|
gaAiReferrals.medium,
|
|
60969
61121
|
gaAiReferrals.trafficClass,
|
|
@@ -60978,7 +61130,7 @@ async function ga4Routes(app, opts) {
|
|
|
60978
61130
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
60979
61131
|
channelGroup: gaAiReferrals.channelGroup,
|
|
60980
61132
|
sessions: gaAiReferrals.sessions
|
|
60981
|
-
}).from(gaAiReferrals).where(
|
|
61133
|
+
}).from(gaAiReferrals).where(and74(...aiConditions)).all();
|
|
60982
61134
|
const aiReferrals = keepWinningDimensionRows(aiReferralRows, (r) => r.source);
|
|
60983
61135
|
const aiReferralLandingPages = keepWinningDimensionRows(
|
|
60984
61136
|
aiReferralLandingPageRows,
|
|
@@ -60995,16 +61147,16 @@ async function ga4Routes(app, opts) {
|
|
|
60995
61147
|
// dates counts a returning visitor once per day they returned. Same
|
|
60996
61148
|
// conclusion the AI-referral surfaces reached in 4.135.0.
|
|
60997
61149
|
sessions: sql37`SUM(${gaSocialReferrals.sessions})`
|
|
60998
|
-
}).from(gaSocialReferrals).where(
|
|
61150
|
+
}).from(gaSocialReferrals).where(and74(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql37`SUM(${gaSocialReferrals.sessions}) DESC`).all();
|
|
60999
61151
|
const socialDailyRows = app.db.select({
|
|
61000
61152
|
date: gaSocialReferrals.date,
|
|
61001
61153
|
source: gaSocialReferrals.source,
|
|
61002
61154
|
sessions: sql37`SUM(${gaSocialReferrals.sessions})`
|
|
61003
|
-
}).from(gaSocialReferrals).where(
|
|
61155
|
+
}).from(gaSocialReferrals).where(and74(...socialConditions)).groupBy(gaSocialReferrals.date, gaSocialReferrals.source).all();
|
|
61004
61156
|
const socialTotals = app.db.select({
|
|
61005
61157
|
sessions: sql37`SUM(${gaSocialReferrals.sessions})`
|
|
61006
|
-
}).from(gaSocialReferrals).where(
|
|
61007
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
61158
|
+
}).from(gaSocialReferrals).where(and74(...socialConditions)).get();
|
|
61159
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
61008
61160
|
const total = summaryRow?.totalSessions ?? 0;
|
|
61009
61161
|
const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
|
|
61010
61162
|
const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
|
|
@@ -61126,8 +61278,8 @@ async function ga4Routes(app, opts) {
|
|
|
61126
61278
|
app.get("/projects/:name/ga/ai-referral-history", async (request, _reply) => {
|
|
61127
61279
|
const project = resolveProject(app.db, request.params.name);
|
|
61128
61280
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
61129
|
-
const range = resolveDateRange(request.query);
|
|
61130
|
-
const conditions = [
|
|
61281
|
+
const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
|
|
61282
|
+
const conditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
|
|
61131
61283
|
const rows = app.db.select({
|
|
61132
61284
|
date: gaAiReferrals.date,
|
|
61133
61285
|
source: gaAiReferrals.source,
|
|
@@ -61140,7 +61292,7 @@ async function ga4Routes(app, opts) {
|
|
|
61140
61292
|
// added up. ga4AiReferralDtoSchema.users was deprecated for this in
|
|
61141
61293
|
// 4.135.0; the history endpoint kept emitting one.
|
|
61142
61294
|
sessions: sql37`SUM(${gaAiReferrals.sessions})`
|
|
61143
|
-
}).from(gaAiReferrals).where(
|
|
61295
|
+
}).from(gaAiReferrals).where(and74(...conditions)).groupBy(
|
|
61144
61296
|
gaAiReferrals.date,
|
|
61145
61297
|
gaAiReferrals.source,
|
|
61146
61298
|
gaAiReferrals.medium,
|
|
@@ -61153,8 +61305,8 @@ async function ga4Routes(app, opts) {
|
|
|
61153
61305
|
app.get("/projects/:name/ga/ai-referral-daily", async (request, _reply) => {
|
|
61154
61306
|
const project = resolveProject(app.db, request.params.name);
|
|
61155
61307
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
61156
|
-
const range = resolveDateRange(request.query);
|
|
61157
|
-
const conditions = [
|
|
61308
|
+
const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
|
|
61309
|
+
const conditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
|
|
61158
61310
|
const rows = app.db.select({
|
|
61159
61311
|
date: gaAiReferrals.date,
|
|
61160
61312
|
source: gaAiReferrals.source,
|
|
@@ -61163,17 +61315,17 @@ async function ga4Routes(app, opts) {
|
|
|
61163
61315
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
61164
61316
|
channelGroup: gaAiReferrals.channelGroup,
|
|
61165
61317
|
sessions: gaAiReferrals.sessions
|
|
61166
|
-
}).from(gaAiReferrals).where(
|
|
61318
|
+
}).from(gaAiReferrals).where(and74(...conditions)).all();
|
|
61167
61319
|
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(
|
|
61320
|
+
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);
|
|
61321
|
+
const firstAiDate = app.db.select({ date: sql37`MIN(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq86(gaAiReferrals.projectId, project.id)).get()?.date ?? null;
|
|
61170
61322
|
return { ...series, trend: aiReferralDailyTrend(series.days, measuredDates, firstAiDate, /* @__PURE__ */ new Date()) };
|
|
61171
61323
|
});
|
|
61172
61324
|
app.get("/projects/:name/ga/social-referral-history", async (request, _reply) => {
|
|
61173
61325
|
const project = resolveProject(app.db, request.params.name);
|
|
61174
61326
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
61175
|
-
const range = resolveDateRange(request.query);
|
|
61176
|
-
const conditions = [
|
|
61327
|
+
const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
|
|
61328
|
+
const conditions = [eq86(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, range)];
|
|
61177
61329
|
const rows = app.db.select({
|
|
61178
61330
|
date: gaSocialReferrals.date,
|
|
61179
61331
|
source: gaSocialReferrals.source,
|
|
@@ -61181,7 +61333,7 @@ async function ga4Routes(app, opts) {
|
|
|
61181
61333
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
61182
61334
|
sessions: gaSocialReferrals.sessions,
|
|
61183
61335
|
users: gaSocialReferrals.users
|
|
61184
|
-
}).from(gaSocialReferrals).where(
|
|
61336
|
+
}).from(gaSocialReferrals).where(and74(...conditions)).orderBy(gaSocialReferrals.date).all();
|
|
61185
61337
|
return rows;
|
|
61186
61338
|
});
|
|
61187
61339
|
app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
|
|
@@ -61194,8 +61346,8 @@ async function ga4Routes(app, opts) {
|
|
|
61194
61346
|
d.setDate(d.getDate() - n);
|
|
61195
61347
|
return fmt(d);
|
|
61196
61348
|
};
|
|
61197
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
61198
|
-
|
|
61349
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and74(
|
|
61350
|
+
eq86(gaSocialReferrals.projectId, project.id),
|
|
61199
61351
|
sql37`${gaSocialReferrals.date} >= ${from}`,
|
|
61200
61352
|
sql37`${gaSocialReferrals.date} < ${to}`
|
|
61201
61353
|
)).get();
|
|
@@ -61206,16 +61358,16 @@ async function ga4Routes(app, opts) {
|
|
|
61206
61358
|
const sourceCurrent = app.db.select({
|
|
61207
61359
|
source: gaSocialReferrals.source,
|
|
61208
61360
|
sessions: sql37`SUM(${gaSocialReferrals.sessions})`
|
|
61209
|
-
}).from(gaSocialReferrals).where(
|
|
61210
|
-
|
|
61361
|
+
}).from(gaSocialReferrals).where(and74(
|
|
61362
|
+
eq86(gaSocialReferrals.projectId, project.id),
|
|
61211
61363
|
sql37`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
|
|
61212
61364
|
sql37`${gaSocialReferrals.date} < ${fmt(today)}`
|
|
61213
61365
|
)).groupBy(gaSocialReferrals.source).all();
|
|
61214
61366
|
const sourcePrev = app.db.select({
|
|
61215
61367
|
source: gaSocialReferrals.source,
|
|
61216
61368
|
sessions: sql37`SUM(${gaSocialReferrals.sessions})`
|
|
61217
|
-
}).from(gaSocialReferrals).where(
|
|
61218
|
-
|
|
61369
|
+
}).from(gaSocialReferrals).where(and74(
|
|
61370
|
+
eq86(gaSocialReferrals.projectId, project.id),
|
|
61219
61371
|
sql37`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
|
|
61220
61372
|
sql37`${gaSocialReferrals.date} < ${daysAgo(7)}`
|
|
61221
61373
|
)).groupBy(gaSocialReferrals.source).all();
|
|
@@ -61240,16 +61392,16 @@ async function ga4Routes(app, opts) {
|
|
|
61240
61392
|
d.setDate(d.getDate() - n);
|
|
61241
61393
|
return fmt(d);
|
|
61242
61394
|
};
|
|
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
|
-
|
|
61395
|
+
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();
|
|
61396
|
+
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();
|
|
61397
|
+
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();
|
|
61398
|
+
const sumAi = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
|
|
61399
|
+
eq86(gaAiReferrals.projectId, project.id),
|
|
61248
61400
|
sql37`${gaAiReferrals.date} >= ${from}`,
|
|
61249
61401
|
sql37`${gaAiReferrals.date} < ${to}`,
|
|
61250
|
-
|
|
61402
|
+
eq86(gaAiReferrals.sourceDimension, "session")
|
|
61251
61403
|
)).get();
|
|
61252
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
61404
|
+
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
61405
|
const todayStr = fmt(today);
|
|
61254
61406
|
const buildTrend = (sum) => {
|
|
61255
61407
|
const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
|
|
@@ -61258,20 +61410,20 @@ async function ga4Routes(app, opts) {
|
|
|
61258
61410
|
const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
|
|
61259
61411
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: deltaPercent(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: deltaPercent(c30, p30) };
|
|
61260
61412
|
};
|
|
61261
|
-
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
61262
|
-
|
|
61413
|
+
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
|
|
61414
|
+
eq86(gaAiReferrals.projectId, project.id),
|
|
61263
61415
|
sql37`${gaAiReferrals.date} >= ${daysAgo(7)}`,
|
|
61264
61416
|
sql37`${gaAiReferrals.date} < ${todayStr}`,
|
|
61265
|
-
|
|
61417
|
+
eq86(gaAiReferrals.sourceDimension, "session")
|
|
61266
61418
|
)).groupBy(gaAiReferrals.source).all();
|
|
61267
|
-
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
61268
|
-
|
|
61419
|
+
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
|
|
61420
|
+
eq86(gaAiReferrals.projectId, project.id),
|
|
61269
61421
|
sql37`${gaAiReferrals.date} >= ${daysAgo(14)}`,
|
|
61270
61422
|
sql37`${gaAiReferrals.date} < ${daysAgo(7)}`,
|
|
61271
|
-
|
|
61423
|
+
eq86(gaAiReferrals.sourceDimension, "session")
|
|
61272
61424
|
)).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(
|
|
61425
|
+
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();
|
|
61426
|
+
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
61427
|
const response = {
|
|
61276
61428
|
total: buildTrend(sumTotal),
|
|
61277
61429
|
organic: buildTrend(sumOrganic),
|
|
@@ -61287,14 +61439,14 @@ async function ga4Routes(app, opts) {
|
|
|
61287
61439
|
const project = resolveProject(app.db, request.params.name);
|
|
61288
61440
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
61289
61441
|
const window2 = parseGaOverviewWindow(request.query.window);
|
|
61290
|
-
const periodRows = app.db.select().from(gaWindowMetrics).where(
|
|
61442
|
+
const periodRows = app.db.select().from(gaWindowMetrics).where(and74(eq86(gaWindowMetrics.projectId, project.id), eq86(gaWindowMetrics.windowKey, window2))).all();
|
|
61291
61443
|
const current = periodRows.find((row) => row.period === GaOverviewPeriods.current) ?? null;
|
|
61292
61444
|
const previous = periodRows.find((row) => row.period === GaOverviewPeriods.previous) ?? null;
|
|
61293
61445
|
const fallback = gaOverviewComparisonRanges(window2);
|
|
61294
61446
|
const from = previous?.startDate ?? fallback.previous.startDate;
|
|
61295
61447
|
const to = current?.endDate ?? fallback.current.endDate;
|
|
61296
|
-
const daily = app.db.select().from(gaDailyTotals).where(
|
|
61297
|
-
|
|
61448
|
+
const daily = app.db.select().from(gaDailyTotals).where(and74(
|
|
61449
|
+
eq86(gaDailyTotals.projectId, project.id),
|
|
61298
61450
|
sql37`${gaDailyTotals.date} >= ${from}`,
|
|
61299
61451
|
sql37`${gaDailyTotals.date} <= ${to}`
|
|
61300
61452
|
)).all();
|
|
@@ -61303,16 +61455,16 @@ async function ga4Routes(app, opts) {
|
|
|
61303
61455
|
app.get("/projects/:name/ga/session-history", async (request, _reply) => {
|
|
61304
61456
|
const project = resolveProject(app.db, request.params.name);
|
|
61305
61457
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
61306
|
-
const range = resolveDateRange(request.query);
|
|
61307
|
-
const conditions = [
|
|
61458
|
+
const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
|
|
61459
|
+
const conditions = [eq86(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, range)];
|
|
61308
61460
|
const rows = app.db.select({
|
|
61309
61461
|
date: gaTrafficSnapshots.date,
|
|
61310
61462
|
sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
|
|
61311
61463
|
organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
61312
61464
|
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(
|
|
61465
|
+
}).from(gaTrafficSnapshots).where(and74(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
61466
|
+
const totalConditions = [eq86(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range)];
|
|
61467
|
+
const totals = app.db.select({ date: gaDailyTotals.date, users: gaDailyTotals.users }).from(gaDailyTotals).where(and74(...totalConditions)).all();
|
|
61316
61468
|
return buildSessionHistory(
|
|
61317
61469
|
rows.map((r) => ({
|
|
61318
61470
|
date: r.date,
|
|
@@ -61331,7 +61483,7 @@ async function ga4Routes(app, opts) {
|
|
|
61331
61483
|
sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
|
|
61332
61484
|
organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
61333
61485
|
users: sql37`SUM(${gaTrafficSnapshots.users})`
|
|
61334
|
-
}).from(gaTrafficSnapshots).where(
|
|
61486
|
+
}).from(gaTrafficSnapshots).where(eq86(gaTrafficSnapshots.projectId, project.id)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
|
|
61335
61487
|
return {
|
|
61336
61488
|
pages: trafficPages.map((r) => ({
|
|
61337
61489
|
landingPage: r.landingPage,
|
|
@@ -61344,7 +61496,7 @@ async function ga4Routes(app, opts) {
|
|
|
61344
61496
|
}
|
|
61345
61497
|
|
|
61346
61498
|
// ../api-routes/src/ga-acquisition.ts
|
|
61347
|
-
import { and as
|
|
61499
|
+
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
61500
|
async function gaAcquisitionRoutes(app) {
|
|
61349
61501
|
app.get("/projects/:name/ga/acquisition", async (request) => {
|
|
61350
61502
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -61367,14 +61519,14 @@ async function gaAcquisitionRoutes(app) {
|
|
|
61367
61519
|
hostName: gaAcquisitionDaily.hostName,
|
|
61368
61520
|
landingPage
|
|
61369
61521
|
};
|
|
61370
|
-
const windowFilters = [
|
|
61522
|
+
const windowFilters = [eq87(gaAcquisitionDaily.projectId, project.id)];
|
|
61371
61523
|
if (range.startDate) windowFilters.push(gte24(gaAcquisitionDaily.date, range.startDate));
|
|
61372
61524
|
if (range.endDate) windowFilters.push(lte24(gaAcquisitionDaily.date, range.endDate));
|
|
61373
61525
|
const filters = [...windowFilters];
|
|
61374
61526
|
if (query2.channel !== void 0) filters.push(sql38`lower(${gaAcquisitionDaily.channelGroup}) = lower(${query2.channel})`);
|
|
61375
61527
|
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(
|
|
61528
|
+
if (query2.source !== void 0) filters.push(eq87(gaAcquisitionDaily.source, query2.source));
|
|
61529
|
+
if (query2.medium !== void 0) filters.push(eq87(gaAcquisitionDaily.medium, query2.medium));
|
|
61378
61530
|
const selectedLandingPage = query2.landingPage === void 0 ? void 0 : normalizeUrlPath(query2.landingPage) ?? query2.landingPage;
|
|
61379
61531
|
if (selectedLandingPage !== void 0) filters.push(sql38`${landingPage} = ${selectedLandingPage}`);
|
|
61380
61532
|
const pathPrefix = normalizePathPrefix(query2.pathPrefix);
|
|
@@ -61382,7 +61534,7 @@ async function gaAcquisitionRoutes(app) {
|
|
|
61382
61534
|
const pathname = sql38`case when instr(${landingPage}, '?') = 0 then ${landingPage} else substr(${landingPage}, 1, instr(${landingPage}, '?') - 1) end`;
|
|
61383
61535
|
filters.push(literalPathPrefixCondition(pathname, pathPrefix));
|
|
61384
61536
|
}
|
|
61385
|
-
const where =
|
|
61537
|
+
const where = and75(...filters);
|
|
61386
61538
|
const sessions = sql38`coalesce(sum(${gaAcquisitionDaily.sessions}), 0)`.mapWith(Number);
|
|
61387
61539
|
const selected = Object.fromEntries(groupBy.map((key) => [key, dimensions[key]]));
|
|
61388
61540
|
const grouped = app.db.select({ ...selected, sessions }).from(gaAcquisitionDaily).where(where).groupBy(...groupBy.map((key) => dimensions[key]));
|
|
@@ -61394,9 +61546,9 @@ async function gaAcquisitionRoutes(app) {
|
|
|
61394
61546
|
...groupBy.filter((key) => key !== GaAcquisitionDimensions.date).map((key) => asc22(dimensions[key]))
|
|
61395
61547
|
];
|
|
61396
61548
|
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(
|
|
61549
|
+
const coverage = app.db.select({ earliestDate: sql38`min(${gaAcquisitionDaily.date})`, latestDate: sql38`max(${gaAcquisitionDaily.date})` }).from(gaAcquisitionDaily).where(eq87(gaAcquisitionDaily.projectId, project.id)).get();
|
|
61550
|
+
const channelGroups = app.db.selectDistinct({ channel: gaAcquisitionDaily.channelGroup }).from(gaAcquisitionDaily).where(and75(...windowFilters)).orderBy(asc22(gaAcquisitionDaily.channelGroup)).all().map((row) => row.channel);
|
|
61551
|
+
const state = app.db.select().from(gaMeasurementSyncStates).where(eq87(gaMeasurementSyncStates.projectId, project.id)).get();
|
|
61400
61552
|
const response = {
|
|
61401
61553
|
source: "ga4-acquisition",
|
|
61402
61554
|
status: state?.acquisitionStatus ?? "never-synced",
|
|
@@ -63128,7 +63280,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
63128
63280
|
|
|
63129
63281
|
// ../api-routes/src/backlinks.ts
|
|
63130
63282
|
import crypto53 from "crypto";
|
|
63131
|
-
import { and as
|
|
63283
|
+
import { and as and78, asc as asc23, desc as desc40, eq as eq89, sql as sql39 } from "drizzle-orm";
|
|
63132
63284
|
|
|
63133
63285
|
// ../integration-commoncrawl/src/constants.ts
|
|
63134
63286
|
import os2 from "os";
|
|
@@ -63578,7 +63730,7 @@ function pruneCachedRelease(release, opts = {}) {
|
|
|
63578
63730
|
}
|
|
63579
63731
|
|
|
63580
63732
|
// ../api-routes/src/backlinks-filter.ts
|
|
63581
|
-
import { and as
|
|
63733
|
+
import { and as and76, ne as ne15, notLike } from "drizzle-orm";
|
|
63582
63734
|
var BACKLINK_FILTER_PATTERNS = [
|
|
63583
63735
|
"*.google.com",
|
|
63584
63736
|
"*.googleusercontent.com",
|
|
@@ -63601,23 +63753,23 @@ function backlinkCrawlerExclusionClause() {
|
|
|
63601
63753
|
conditions.push(ne15(backlinkDomains.linkingDomain, pattern));
|
|
63602
63754
|
}
|
|
63603
63755
|
}
|
|
63604
|
-
const combined =
|
|
63756
|
+
const combined = and76(...conditions);
|
|
63605
63757
|
if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
|
|
63606
63758
|
return combined;
|
|
63607
63759
|
}
|
|
63608
63760
|
|
|
63609
63761
|
// ../api-routes/src/backlinks-source-state.ts
|
|
63610
|
-
import { and as
|
|
63762
|
+
import { and as and77, eq as eq88, isNotNull as isNotNull8, or as or17 } from "drizzle-orm";
|
|
63611
63763
|
function readCommonCrawlSourceState(db, projectId) {
|
|
63612
63764
|
const completedReleases = db.select({ status: ccReleaseSyncs.status }).from(ccReleaseSyncs).where(or17(
|
|
63613
|
-
|
|
63765
|
+
eq88(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready),
|
|
63614
63766
|
isNotNull8(ccReleaseSyncs.queryFinishedAt)
|
|
63615
63767
|
)).all();
|
|
63616
63768
|
const hasReadyRelease = completedReleases.length > 0;
|
|
63617
63769
|
const hasCurrentReadyRelease = completedReleases.some((release) => release.status === CcReleaseSyncStatuses.ready);
|
|
63618
|
-
const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(
|
|
63619
|
-
|
|
63620
|
-
|
|
63770
|
+
const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and77(
|
|
63771
|
+
eq88(backlinkSummaries.projectId, projectId),
|
|
63772
|
+
eq88(backlinkSummaries.source, BacklinkSources.commoncrawl)
|
|
63621
63773
|
)).limit(1).get() !== void 0;
|
|
63622
63774
|
return { hasReadyRelease, hasCurrentReadyRelease, hasProjectData, connected: hasReadyRelease && hasProjectData };
|
|
63623
63775
|
}
|
|
@@ -63688,11 +63840,11 @@ function mapRunRow(row) {
|
|
|
63688
63840
|
}
|
|
63689
63841
|
function latestSummaryForProject(db, projectId, source, release) {
|
|
63690
63842
|
const conditions = [
|
|
63691
|
-
|
|
63692
|
-
|
|
63843
|
+
eq89(backlinkSummaries.projectId, projectId),
|
|
63844
|
+
eq89(backlinkSummaries.source, source)
|
|
63693
63845
|
];
|
|
63694
|
-
if (release) conditions.push(
|
|
63695
|
-
const rows = db.select().from(backlinkSummaries).where(
|
|
63846
|
+
if (release) conditions.push(eq89(backlinkSummaries.release, release));
|
|
63847
|
+
const rows = db.select().from(backlinkSummaries).where(and78(...conditions)).orderBy(desc40(backlinkSummaries.queriedAt)).all();
|
|
63696
63848
|
return rows.reduce((latest, row) => {
|
|
63697
63849
|
if (!latest) return row;
|
|
63698
63850
|
const newer = isValidReleaseId(row.release) && isValidReleaseId(latest.release) && compareReleaseIds(row.release, latest.release) > 0;
|
|
@@ -63705,12 +63857,12 @@ function parseExcludeCrawlers(value) {
|
|
|
63705
63857
|
return lower === "1" || lower === "true" || lower === "yes";
|
|
63706
63858
|
}
|
|
63707
63859
|
function computeFilteredSummary(db, base) {
|
|
63708
|
-
const baseDomainCondition =
|
|
63709
|
-
|
|
63710
|
-
|
|
63711
|
-
|
|
63860
|
+
const baseDomainCondition = and78(
|
|
63861
|
+
eq89(backlinkDomains.projectId, base.projectId),
|
|
63862
|
+
eq89(backlinkDomains.source, base.source),
|
|
63863
|
+
eq89(backlinkDomains.release, base.release)
|
|
63712
63864
|
);
|
|
63713
|
-
const filteredCondition =
|
|
63865
|
+
const filteredCondition = and78(baseDomainCondition, backlinkCrawlerExclusionClause());
|
|
63714
63866
|
const unfilteredAgg = db.select({
|
|
63715
63867
|
count: sql39`count(*)`,
|
|
63716
63868
|
total: sql39`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
@@ -63751,7 +63903,7 @@ function computeFilteredReleaseTotals(db, projectId, source) {
|
|
|
63751
63903
|
SELECT ${backlinkDomains.release} AS release, ${backlinkDomains.numHosts} AS num_hosts, ${kept} AS kept,
|
|
63752
63904
|
ROW_NUMBER() OVER (PARTITION BY ${backlinkDomains.release}, ${kept} ORDER BY ${backlinkDomains.numHosts} DESC) AS kept_rank
|
|
63753
63905
|
FROM ${backlinkDomains}
|
|
63754
|
-
WHERE ${
|
|
63906
|
+
WHERE ${and78(eq89(backlinkDomains.projectId, projectId), eq89(backlinkDomains.source, source))}
|
|
63755
63907
|
)
|
|
63756
63908
|
GROUP BY release`);
|
|
63757
63909
|
return new Map(rows.map((row) => {
|
|
@@ -63765,13 +63917,13 @@ function computeFilteredReleaseTotals(db, projectId, source) {
|
|
|
63765
63917
|
}));
|
|
63766
63918
|
}
|
|
63767
63919
|
function buildSourceAvailability(db, projectId, source, connected2, excludeCrawlers) {
|
|
63768
|
-
const summary3 = db.select().from(backlinkSummaries).where(
|
|
63920
|
+
const summary3 = db.select().from(backlinkSummaries).where(and78(eq89(backlinkSummaries.projectId, projectId), eq89(backlinkSummaries.source, source))).orderBy(desc40(backlinkSummaries.queriedAt)).limit(1).get();
|
|
63769
63921
|
let totalLinkingDomains = summary3?.totalLinkingDomains ?? 0;
|
|
63770
63922
|
if (summary3 && excludeCrawlers) {
|
|
63771
|
-
const filtered = db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(
|
|
63772
|
-
|
|
63773
|
-
|
|
63774
|
-
|
|
63923
|
+
const filtered = db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(and78(
|
|
63924
|
+
eq89(backlinkDomains.projectId, projectId),
|
|
63925
|
+
eq89(backlinkDomains.source, source),
|
|
63926
|
+
eq89(backlinkDomains.release, summary3.release),
|
|
63775
63927
|
backlinkCrawlerExclusionClause()
|
|
63776
63928
|
)).get();
|
|
63777
63929
|
totalLinkingDomains = Number(filtered?.count ?? 0);
|
|
@@ -63841,7 +63993,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63841
63993
|
"@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
|
|
63842
63994
|
);
|
|
63843
63995
|
}
|
|
63844
|
-
const existing = app.db.select().from(ccReleaseSyncs).where(
|
|
63996
|
+
const existing = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.release, release)).get();
|
|
63845
63997
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
63846
63998
|
if (existing) {
|
|
63847
63999
|
if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
|
|
@@ -63852,9 +64004,9 @@ async function backlinksRoutes(app, opts) {
|
|
|
63852
64004
|
phaseDetail: null,
|
|
63853
64005
|
error: null,
|
|
63854
64006
|
updatedAt: now
|
|
63855
|
-
}).where(
|
|
64007
|
+
}).where(eq89(ccReleaseSyncs.id, existing.id)).run();
|
|
63856
64008
|
opts.onReleaseSyncRequested(existing.id, release);
|
|
63857
|
-
const refreshed = app.db.select().from(ccReleaseSyncs).where(
|
|
64009
|
+
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.id, existing.id)).get();
|
|
63858
64010
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
63859
64011
|
}
|
|
63860
64012
|
const id = crypto53.randomUUID();
|
|
@@ -63866,7 +64018,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63866
64018
|
updatedAt: now
|
|
63867
64019
|
}).run();
|
|
63868
64020
|
opts.onReleaseSyncRequested(id, release);
|
|
63869
|
-
const inserted = app.db.select().from(ccReleaseSyncs).where(
|
|
64021
|
+
const inserted = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.id, id)).get();
|
|
63870
64022
|
return reply.status(201).send(mapSyncRow(inserted));
|
|
63871
64023
|
});
|
|
63872
64024
|
app.get("/backlinks/syncs/latest", async (_request, reply) => {
|
|
@@ -63924,7 +64076,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63924
64076
|
createdAt: now
|
|
63925
64077
|
}).run();
|
|
63926
64078
|
opts.onBacklinkExtractRequested(runId, project.id, release);
|
|
63927
|
-
const run = app.db.select().from(runs).where(
|
|
64079
|
+
const run = app.db.select().from(runs).where(eq89(runs.id, runId)).get();
|
|
63928
64080
|
return reply.status(201).send(mapRunRow(run));
|
|
63929
64081
|
});
|
|
63930
64082
|
app.get(
|
|
@@ -63950,12 +64102,12 @@ async function backlinksRoutes(app, opts) {
|
|
|
63950
64102
|
const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
|
|
63951
64103
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
63952
64104
|
const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
|
|
63953
|
-
const baseDomainCondition =
|
|
63954
|
-
|
|
63955
|
-
|
|
63956
|
-
|
|
64105
|
+
const baseDomainCondition = and78(
|
|
64106
|
+
eq89(backlinkDomains.projectId, project.id),
|
|
64107
|
+
eq89(backlinkDomains.source, source),
|
|
64108
|
+
eq89(backlinkDomains.release, targetRelease)
|
|
63957
64109
|
);
|
|
63958
|
-
const domainCondition = excludeCrawlers ?
|
|
64110
|
+
const domainCondition = excludeCrawlers ? and78(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
|
|
63959
64111
|
const totalRow = app.db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
63960
64112
|
const rows = app.db.select({
|
|
63961
64113
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
@@ -63979,7 +64131,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
63979
64131
|
async (request, reply) => {
|
|
63980
64132
|
const project = resolveProject(app.db, request.params.name);
|
|
63981
64133
|
const source = parseSourceParam(request.query.source);
|
|
63982
|
-
const rows = app.db.select().from(backlinkSummaries).where(
|
|
64134
|
+
const rows = app.db.select().from(backlinkSummaries).where(and78(eq89(backlinkSummaries.projectId, project.id), eq89(backlinkSummaries.source, source))).orderBy(asc23(backlinkSummaries.queriedAt)).all();
|
|
63983
64135
|
const response = rows.map((r) => ({
|
|
63984
64136
|
release: r.release,
|
|
63985
64137
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -64006,7 +64158,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
64006
64158
|
import crypto55 from "crypto";
|
|
64007
64159
|
import { isIP as isIP3 } from "net";
|
|
64008
64160
|
import { isDeepStrictEqual } from "util";
|
|
64009
|
-
import { and as
|
|
64161
|
+
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
64162
|
|
|
64011
64163
|
// ../api-routes/src/traffic-limits.ts
|
|
64012
64164
|
var VERCEL_MAX_SYNC_WINDOW_MS = 24 * 60 * 6e4;
|
|
@@ -65349,7 +65501,7 @@ async function ackCloudflareQueueMessages(client, options) {
|
|
|
65349
65501
|
}
|
|
65350
65502
|
|
|
65351
65503
|
// ../api-routes/src/traffic-sync-lease.ts
|
|
65352
|
-
import { and as
|
|
65504
|
+
import { and as and79, eq as eq90, isNull as isNull13, lte as lte25, or as or18 } from "drizzle-orm";
|
|
65353
65505
|
function leaseExpiresAt(now, ttlMs) {
|
|
65354
65506
|
const nowMs = Date.parse(now);
|
|
65355
65507
|
if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
|
|
@@ -65362,12 +65514,12 @@ function tryClaimTrafficSyncLease(input) {
|
|
|
65362
65514
|
const changed = tx.update(trafficSources).set({
|
|
65363
65515
|
syncLeaseOwner: input.owner,
|
|
65364
65516
|
syncLeaseExpiresAt: expiresAt
|
|
65365
|
-
}).where(
|
|
65366
|
-
|
|
65517
|
+
}).where(and79(
|
|
65518
|
+
eq90(trafficSources.id, input.sourceId),
|
|
65367
65519
|
or18(
|
|
65368
65520
|
isNull13(trafficSources.syncLeaseOwner),
|
|
65369
65521
|
lte25(trafficSources.syncLeaseExpiresAt, input.now),
|
|
65370
|
-
|
|
65522
|
+
eq90(trafficSources.syncLeaseOwner, input.owner)
|
|
65371
65523
|
)
|
|
65372
65524
|
)).run();
|
|
65373
65525
|
return changed.changes === 1;
|
|
@@ -65378,9 +65530,9 @@ function releaseTrafficSyncLease(input) {
|
|
|
65378
65530
|
const changed = tx.update(trafficSources).set({
|
|
65379
65531
|
syncLeaseOwner: null,
|
|
65380
65532
|
syncLeaseExpiresAt: null
|
|
65381
|
-
}).where(
|
|
65382
|
-
|
|
65383
|
-
|
|
65533
|
+
}).where(and79(
|
|
65534
|
+
eq90(trafficSources.id, input.sourceId),
|
|
65535
|
+
eq90(trafficSources.syncLeaseOwner, input.owner)
|
|
65384
65536
|
)).run();
|
|
65385
65537
|
return changed.changes === 1;
|
|
65386
65538
|
}, { behavior: "immediate" });
|
|
@@ -65447,14 +65599,14 @@ async function loadQueueReceiptPolicy(db, sourceId, readRetentionSeconds, warn2)
|
|
|
65447
65599
|
if (liveTtlMs === null) return { ttlMsAt: () => CLOUDFLARE_QUEUE_RECEIPT_TTL_MS };
|
|
65448
65600
|
const ttlMs = liveTtlMs;
|
|
65449
65601
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
65450
|
-
const alive =
|
|
65602
|
+
const alive = and80(eq91(trafficEventReceipts.sourceId, sourceId), gt7(trafficEventReceipts.expiresAt, now));
|
|
65451
65603
|
const lifetimeMs = sql40`(julianday(${trafficEventReceipts.expiresAt}) - julianday(${trafficEventReceipts.receivedAt})) * ${DAY_MS5}`;
|
|
65452
65604
|
const shortest = db.select({ ms: sql40`min(${lifetimeMs})` }).from(trafficEventReceipts).where(alive).get()?.ms ?? null;
|
|
65453
65605
|
if (shortest !== null && shortest < ttlMs - 1e3) {
|
|
65454
65606
|
const extended = sql40`strftime('%Y-%m-%dT%H:%M:%fZ', julianday(${trafficEventReceipts.receivedAt}) + ${ttlMs / DAY_MS5})`;
|
|
65455
|
-
db.update(trafficEventReceipts).set({ expiresAt: extended }).where(
|
|
65607
|
+
db.update(trafficEventReceipts).set({ expiresAt: extended }).where(and80(alive, lt18(trafficEventReceipts.expiresAt, extended))).run();
|
|
65456
65608
|
}
|
|
65457
|
-
const floorExpiry = db.select({ at: sql40`max(${trafficEventReceipts.expiresAt})` }).from(trafficEventReceipts).where(
|
|
65609
|
+
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
65610
|
const floorMs = floorExpiry ? Date.parse(floorExpiry) : 0;
|
|
65459
65611
|
return { ttlMsAt: (receivedAt) => Math.max(ttlMs, floorMs - Date.parse(receivedAt)) };
|
|
65460
65612
|
}
|
|
@@ -65509,7 +65661,7 @@ function cloudflareIngestRateLimitKey(request, store, db) {
|
|
|
65509
65661
|
const authenticated = authenticateCloudflareIngest(request, store);
|
|
65510
65662
|
const params = request.params;
|
|
65511
65663
|
if (authenticated && typeof params.name === "string" && authenticated.credential.projectName === params.name) {
|
|
65512
|
-
const source = db.select().from(trafficSources).where(
|
|
65664
|
+
const source = db.select().from(trafficSources).where(eq91(trafficSources.id, authenticated.credential.sourceId)).get();
|
|
65513
65665
|
if (source && source.sourceType === TrafficSourceTypes.cloudflare && source.status === TrafficSourceStatuses.connected && parseDirectPushCloudflareSourceConfig(source.configJson) && timingSafeEqualHex(authenticated.bearerHash, source.ingestTokenHash)) {
|
|
65514
65666
|
return `cloudflare-source:${authenticated.credential.sourceId}`;
|
|
65515
65667
|
}
|
|
@@ -65752,7 +65904,7 @@ async function defaultResolveAccessToken(record2) {
|
|
|
65752
65904
|
);
|
|
65753
65905
|
}
|
|
65754
65906
|
function hasConnectedTrafficSourceSibling(db, projectId, sourceId) {
|
|
65755
|
-
return db.select().from(trafficSources).where(
|
|
65907
|
+
return db.select().from(trafficSources).where(eq91(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
|
|
65756
65908
|
}
|
|
65757
65909
|
function isAuthoritativeTrafficSource(db, source) {
|
|
65758
65910
|
return (source.status === TrafficSourceStatuses.connected || source.status === TrafficSourceStatuses.error) && !hasConnectedTrafficSourceSibling(db, source.projectId, source.id);
|
|
@@ -65779,13 +65931,13 @@ function wordpressBaseUrlIdentity(value) {
|
|
|
65779
65931
|
}
|
|
65780
65932
|
}
|
|
65781
65933
|
function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing = true) {
|
|
65782
|
-
const schedule = tx.select().from(schedules).where(
|
|
65783
|
-
|
|
65784
|
-
|
|
65934
|
+
const schedule = tx.select().from(schedules).where(and80(
|
|
65935
|
+
eq91(schedules.projectId, projectId),
|
|
65936
|
+
eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
65785
65937
|
)).get();
|
|
65786
65938
|
if (schedule) {
|
|
65787
65939
|
if (schedule.sourceId === sourceId) return { changed: false, created: false };
|
|
65788
|
-
tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(
|
|
65940
|
+
tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq91(schedules.id, schedule.id)).run();
|
|
65789
65941
|
return { changed: true, created: false };
|
|
65790
65942
|
}
|
|
65791
65943
|
if (!createIfMissing) return { changed: false, created: false };
|
|
@@ -65827,7 +65979,7 @@ async function runBackfillTask(options) {
|
|
|
65827
65979
|
};
|
|
65828
65980
|
const renewLease2 = setInterval(() => {
|
|
65829
65981
|
try {
|
|
65830
|
-
const latest = app.db.select().from(trafficSources).where(
|
|
65982
|
+
const latest = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
65831
65983
|
if (!latest || !isAuthoritativeTrafficSource(app.db, latest) || !isSameTrafficSourceGeneration(latest, sourceRow2)) {
|
|
65832
65984
|
clearInterval(renewLease2);
|
|
65833
65985
|
stopped.abort();
|
|
@@ -65875,10 +66027,10 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65875
66027
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
65876
66028
|
try {
|
|
65877
66029
|
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(
|
|
66030
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq91(runs.id, runId)).run();
|
|
66031
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
65880
66032
|
if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
|
|
65881
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
66033
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
65882
66034
|
}
|
|
65883
66035
|
});
|
|
65884
66036
|
} catch {
|
|
@@ -65910,7 +66062,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65910
66062
|
let emptyOutcome;
|
|
65911
66063
|
try {
|
|
65912
66064
|
const completed = app.db.transaction((tx) => {
|
|
65913
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
66065
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
65914
66066
|
const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner);
|
|
65915
66067
|
if (stillAuthoritative) {
|
|
65916
66068
|
enforceRawEventSampleRetention(tx, sourceRow2.id, finishedAt2);
|
|
@@ -65919,7 +66071,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65919
66071
|
status: RunStatuses.failed,
|
|
65920
66072
|
error: "Traffic source was deactivated or reconfigured during backfill",
|
|
65921
66073
|
finishedAt: finishedAt2
|
|
65922
|
-
}).where(
|
|
66074
|
+
}).where(eq91(runs.id, runId)).run();
|
|
65923
66075
|
return Boolean(stillAuthoritative);
|
|
65924
66076
|
});
|
|
65925
66077
|
emptyOutcome = completed ? { status: OutcomeStatuses.skipped, reasonCode: OutcomeReasonCodes.NO_DATA } : { status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_CONNECTED };
|
|
@@ -65951,13 +66103,13 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65951
66103
|
const nextLastSyncedAt = Math.max(currentLastSyncedMs, windowEnd.getTime()) === windowEnd.getTime() ? windowEndIso : sourceRow2.lastSyncedAt;
|
|
65952
66104
|
try {
|
|
65953
66105
|
const commitOutcome = app.db.transaction((tx) => {
|
|
65954
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
66106
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
65955
66107
|
if (latestSource?.sourceType === TrafficSourceTypes.wordpress) {
|
|
65956
66108
|
tx.update(runs).set({
|
|
65957
66109
|
status: RunStatuses.failed,
|
|
65958
66110
|
error: "Generic WordPress replace backfill is unavailable because retained coverage is unproven.",
|
|
65959
66111
|
finishedAt
|
|
65960
|
-
}).where(
|
|
66112
|
+
}).where(eq91(runs.id, runId)).run();
|
|
65961
66113
|
return "wordpress-backfill-unsupported";
|
|
65962
66114
|
}
|
|
65963
66115
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
|
|
@@ -65965,7 +66117,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65965
66117
|
status: RunStatuses.failed,
|
|
65966
66118
|
error: "Traffic source was deactivated or reconfigured during backfill",
|
|
65967
66119
|
finishedAt
|
|
65968
|
-
}).where(
|
|
66120
|
+
}).where(eq91(runs.id, runId)).run();
|
|
65969
66121
|
return "source-inactive";
|
|
65970
66122
|
}
|
|
65971
66123
|
const rawSampleCutoff = enforceRawEventSampleRetention(
|
|
@@ -65974,37 +66126,37 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
65974
66126
|
finishedAt
|
|
65975
66127
|
);
|
|
65976
66128
|
tx.delete(crawlerEventsHourly).where(
|
|
65977
|
-
|
|
65978
|
-
|
|
66129
|
+
and80(
|
|
66130
|
+
eq91(crawlerEventsHourly.sourceId, sourceRow2.id),
|
|
65979
66131
|
gte25(crawlerEventsHourly.tsHour, windowStartIso),
|
|
65980
66132
|
lte26(crawlerEventsHourly.tsHour, windowEndIso)
|
|
65981
66133
|
)
|
|
65982
66134
|
).run();
|
|
65983
66135
|
tx.delete(aiUserFetchEventsHourly).where(
|
|
65984
|
-
|
|
65985
|
-
|
|
66136
|
+
and80(
|
|
66137
|
+
eq91(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
|
|
65986
66138
|
gte25(aiUserFetchEventsHourly.tsHour, windowStartIso),
|
|
65987
66139
|
lte26(aiUserFetchEventsHourly.tsHour, windowEndIso)
|
|
65988
66140
|
)
|
|
65989
66141
|
).run();
|
|
65990
66142
|
clearLikelyFakeAgentHits(tx, { sourceId: sourceRow2.id, windowStart: windowStartIso, windowEnd: windowEndIso });
|
|
65991
66143
|
tx.delete(aiReferralEventsHourly).where(
|
|
65992
|
-
|
|
65993
|
-
|
|
66144
|
+
and80(
|
|
66145
|
+
eq91(aiReferralEventsHourly.sourceId, sourceRow2.id),
|
|
65994
66146
|
gte25(aiReferralEventsHourly.tsHour, windowStartIso),
|
|
65995
66147
|
lte26(aiReferralEventsHourly.tsHour, windowEndIso)
|
|
65996
66148
|
)
|
|
65997
66149
|
).run();
|
|
65998
66150
|
tx.delete(rawEventSamples).where(
|
|
65999
|
-
|
|
66000
|
-
|
|
66151
|
+
and80(
|
|
66152
|
+
eq91(rawEventSamples.sourceId, sourceRow2.id),
|
|
66001
66153
|
gte25(rawEventSamples.ts, windowStartIso),
|
|
66002
66154
|
lte26(rawEventSamples.ts, windowEndIso)
|
|
66003
66155
|
)
|
|
66004
66156
|
).run();
|
|
66005
66157
|
tx.delete(unrecognizedAgentEventsHourly).where(
|
|
66006
|
-
|
|
66007
|
-
|
|
66158
|
+
and80(
|
|
66159
|
+
eq91(unrecognizedAgentEventsHourly.sourceId, sourceRow2.id),
|
|
66008
66160
|
gte25(unrecognizedAgentEventsHourly.tsHour, windowStartIso),
|
|
66009
66161
|
lte26(unrecognizedAgentEventsHourly.tsHour, windowEndIso)
|
|
66010
66162
|
)
|
|
@@ -66212,9 +66364,9 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
66212
66364
|
FROM _migrations WHERE version = 207
|
|
66213
66365
|
`);
|
|
66214
66366
|
if (ruleChange?.appliedAt) {
|
|
66215
|
-
const olderReferral = tx.select({ sourceId: aiReferralEventsHourly.sourceId }).from(aiReferralEventsHourly).where(
|
|
66216
|
-
|
|
66217
|
-
|
|
66367
|
+
const olderReferral = tx.select({ sourceId: aiReferralEventsHourly.sourceId }).from(aiReferralEventsHourly).where(and80(
|
|
66368
|
+
eq91(aiReferralEventsHourly.sourceId, sourceRow2.id),
|
|
66369
|
+
lt18(aiReferralEventsHourly.createdAt, ruleChange.appliedAt)
|
|
66218
66370
|
)).limit(1).get();
|
|
66219
66371
|
if (!olderReferral) olderCountUntil = null;
|
|
66220
66372
|
}
|
|
@@ -66227,8 +66379,8 @@ async function runBackfillTaskHoldingLease(options, signal) {
|
|
|
66227
66379
|
aiReferralOlderCountUntil: olderCountUntil,
|
|
66228
66380
|
...skipRecovered ? { skippedThroughAt: null } : {},
|
|
66229
66381
|
updatedAt: finishedAt
|
|
66230
|
-
}).where(
|
|
66231
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
66382
|
+
}).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
66383
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq91(runs.id, runId)).run();
|
|
66232
66384
|
return "committed";
|
|
66233
66385
|
});
|
|
66234
66386
|
reportBackfill(commitOutcome === "committed" ? {
|
|
@@ -66401,7 +66553,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66401
66553
|
};
|
|
66402
66554
|
const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
|
|
66403
66555
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
66404
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
66556
|
+
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
66557
|
const sourceId = activeSource?.id ?? crypto55.randomUUID();
|
|
66406
66558
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
66407
66559
|
if (activeSource) {
|
|
@@ -66411,7 +66563,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66411
66563
|
lastError: null,
|
|
66412
66564
|
configJson: config,
|
|
66413
66565
|
updatedAt: now
|
|
66414
|
-
}).where(
|
|
66566
|
+
}).where(eq91(trafficSources.id, sourceId)).run();
|
|
66415
66567
|
} else {
|
|
66416
66568
|
tx.insert(trafficSources).values({
|
|
66417
66569
|
id: sourceId,
|
|
@@ -66437,7 +66589,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66437
66589
|
entityId: sourceId
|
|
66438
66590
|
});
|
|
66439
66591
|
return {
|
|
66440
|
-
sourceRow: tx.select().from(trafficSources).where(
|
|
66592
|
+
sourceRow: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get(),
|
|
66441
66593
|
scheduleChanged: scheduleBinding.changed
|
|
66442
66594
|
};
|
|
66443
66595
|
}, { behavior: "immediate" });
|
|
@@ -66492,7 +66644,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66492
66644
|
const config = { baseUrl, username };
|
|
66493
66645
|
const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
|
|
66494
66646
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
66495
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
66647
|
+
const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
|
|
66496
66648
|
const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
|
|
66497
66649
|
const sourceId = endpointChanged ? crypto55.randomUUID() : activeSource?.id ?? crypto55.randomUUID();
|
|
66498
66650
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
@@ -66503,14 +66655,14 @@ async function trafficRoutes(app, opts) {
|
|
|
66503
66655
|
lastError: null,
|
|
66504
66656
|
configJson: config,
|
|
66505
66657
|
updatedAt: now
|
|
66506
|
-
}).where(
|
|
66658
|
+
}).where(eq91(trafficSources.id, sourceId)).run();
|
|
66507
66659
|
} else {
|
|
66508
66660
|
if (activeSource) {
|
|
66509
66661
|
tx.update(trafficSources).set({
|
|
66510
66662
|
status: TrafficSourceStatuses.archived,
|
|
66511
66663
|
archivedAt: now,
|
|
66512
66664
|
updatedAt: now
|
|
66513
|
-
}).where(
|
|
66665
|
+
}).where(eq91(trafficSources.id, activeSource.id)).run();
|
|
66514
66666
|
}
|
|
66515
66667
|
tx.insert(trafficSources).values({
|
|
66516
66668
|
id: sourceId,
|
|
@@ -66536,7 +66688,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66536
66688
|
entityId: sourceId
|
|
66537
66689
|
});
|
|
66538
66690
|
return {
|
|
66539
|
-
sourceRow: tx.select().from(trafficSources).where(
|
|
66691
|
+
sourceRow: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get(),
|
|
66540
66692
|
scheduleChanged: scheduleBinding.changed
|
|
66541
66693
|
};
|
|
66542
66694
|
}, { behavior: "immediate" });
|
|
@@ -66593,7 +66745,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66593
66745
|
const config = { projectId, teamId, environment };
|
|
66594
66746
|
const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
|
|
66595
66747
|
const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
|
|
66596
|
-
const activeSource = tx.select().from(trafficSources).where(
|
|
66748
|
+
const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
|
|
66597
66749
|
const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
|
|
66598
66750
|
let row;
|
|
66599
66751
|
if (activeSource) {
|
|
@@ -66603,8 +66755,8 @@ async function trafficRoutes(app, opts) {
|
|
|
66603
66755
|
lastError: null,
|
|
66604
66756
|
configJson: config,
|
|
66605
66757
|
updatedAt: now
|
|
66606
|
-
}).where(
|
|
66607
|
-
row = tx.select().from(trafficSources).where(
|
|
66758
|
+
}).where(eq91(trafficSources.id, activeSource.id)).run();
|
|
66759
|
+
row = tx.select().from(trafficSources).where(eq91(trafficSources.id, activeSource.id)).get();
|
|
66608
66760
|
} else {
|
|
66609
66761
|
const newId = crypto55.randomUUID();
|
|
66610
66762
|
tx.insert(trafficSources).values({
|
|
@@ -66629,7 +66781,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66629
66781
|
createdAt: now,
|
|
66630
66782
|
updatedAt: now
|
|
66631
66783
|
}).run();
|
|
66632
|
-
row = tx.select().from(trafficSources).where(
|
|
66784
|
+
row = tx.select().from(trafficSources).where(eq91(trafficSources.id, newId)).get();
|
|
66633
66785
|
}
|
|
66634
66786
|
const scheduleBinding = sourceStatus === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, row.id, now) : { changed: false, created: false };
|
|
66635
66787
|
writeAuditLog(tx, {
|
|
@@ -66673,7 +66825,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66673
66825
|
const { displayName: displayName2, zoneId: zoneId2, accountId: accountId2, queueId, queueName, retentionSeconds, apiToken } = parsed.data;
|
|
66674
66826
|
const workerRouteHost2 = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
|
|
66675
66827
|
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
66676
|
-
const cloudflareSources = app.db.select().from(trafficSources).where(
|
|
66828
|
+
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
66829
|
const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
|
|
66678
66830
|
if (sameModeSource) attempt.update({ action: "reauth" });
|
|
66679
66831
|
const sourceId2 = sameModeSource?.id ?? crypto55.randomUUID();
|
|
@@ -66727,7 +66879,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66727
66879
|
let scheduleChanged = false;
|
|
66728
66880
|
try {
|
|
66729
66881
|
const result = app.db.transaction((tx) => {
|
|
66730
|
-
const currentSameModeSource = tx.select().from(trafficSources).where(
|
|
66882
|
+
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
66883
|
if ((currentSameModeSource?.id ?? null) !== (sameModeSource?.id ?? null)) {
|
|
66732
66884
|
throw operationInProgress("Cloudflare Queue source changed during connect; retry");
|
|
66733
66885
|
}
|
|
@@ -66745,7 +66897,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66745
66897
|
queueBacklogObservedAt: null
|
|
66746
66898
|
} : {},
|
|
66747
66899
|
updatedAt: now2
|
|
66748
|
-
}).where(
|
|
66900
|
+
}).where(eq91(trafficSources.id, sourceId2)).run();
|
|
66749
66901
|
} else {
|
|
66750
66902
|
tx.insert(trafficSources).values({
|
|
66751
66903
|
id: sourceId2,
|
|
@@ -66765,7 +66917,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66765
66917
|
}
|
|
66766
66918
|
const changed = status === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, sourceId2, now2).changed : false;
|
|
66767
66919
|
return {
|
|
66768
|
-
row: tx.select().from(trafficSources).where(
|
|
66920
|
+
row: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId2)).get(),
|
|
66769
66921
|
scheduleChanged: changed
|
|
66770
66922
|
};
|
|
66771
66923
|
}, { behavior: "immediate" });
|
|
@@ -66815,7 +66967,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66815
66967
|
const { deliveryMode, displayName, zoneId, accountId } = parsed.data;
|
|
66816
66968
|
const workerRouteHost = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
|
|
66817
66969
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
66818
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
66970
|
+
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
66971
|
if (activeSource) attempt.update({ action: "reauth" });
|
|
66820
66972
|
const sourceId = activeSource?.id ?? crypto55.randomUUID();
|
|
66821
66973
|
const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
|
|
@@ -66883,7 +67035,7 @@ async function trafficRoutes(app, opts) {
|
|
|
66883
67035
|
let scheduleRemoved = false;
|
|
66884
67036
|
try {
|
|
66885
67037
|
const result = app.db.transaction((tx) => {
|
|
66886
|
-
const currentActiveSource = tx.select().from(trafficSources).where(
|
|
67038
|
+
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
67039
|
if ((currentActiveSource?.id ?? null) !== (activeSource?.id ?? null)) {
|
|
66888
67040
|
throw operationInProgress("Cloudflare direct-push source changed during connect; retry");
|
|
66889
67041
|
}
|
|
@@ -66897,8 +67049,8 @@ async function trafficRoutes(app, opts) {
|
|
|
66897
67049
|
configJson: config,
|
|
66898
67050
|
ingestTokenHash,
|
|
66899
67051
|
updatedAt: now
|
|
66900
|
-
}).where(
|
|
66901
|
-
row = tx.select().from(trafficSources).where(
|
|
67052
|
+
}).where(eq91(trafficSources.id, currentActiveSource.id)).run();
|
|
67053
|
+
row = tx.select().from(trafficSources).where(eq91(trafficSources.id, currentActiveSource.id)).get();
|
|
66902
67054
|
} else {
|
|
66903
67055
|
tx.insert(trafficSources).values({
|
|
66904
67056
|
id: sourceId,
|
|
@@ -66918,16 +67070,16 @@ async function trafficRoutes(app, opts) {
|
|
|
66918
67070
|
createdAt: now,
|
|
66919
67071
|
updatedAt: now
|
|
66920
67072
|
}).run();
|
|
66921
|
-
row = tx.select().from(trafficSources).where(
|
|
67073
|
+
row = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get();
|
|
66922
67074
|
}
|
|
66923
67075
|
let removed = false;
|
|
66924
67076
|
if (row.status === TrafficSourceStatuses.connected) {
|
|
66925
|
-
const schedule = tx.select().from(schedules).where(
|
|
66926
|
-
|
|
66927
|
-
|
|
67077
|
+
const schedule = tx.select().from(schedules).where(and80(
|
|
67078
|
+
eq91(schedules.projectId, project.id),
|
|
67079
|
+
eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
66928
67080
|
)).get();
|
|
66929
67081
|
if (schedule) {
|
|
66930
|
-
tx.delete(schedules).where(
|
|
67082
|
+
tx.delete(schedules).where(eq91(schedules.id, schedule.id)).run();
|
|
66931
67083
|
removed = true;
|
|
66932
67084
|
}
|
|
66933
67085
|
}
|
|
@@ -66983,47 +67135,47 @@ async function trafficRoutes(app, opts) {
|
|
|
66983
67135
|
};
|
|
66984
67136
|
}));
|
|
66985
67137
|
app.post("/projects/:name/traffic/sources/:id/activate", connectionRoute(app, (request) => {
|
|
66986
|
-
const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(
|
|
67138
|
+
const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
|
|
66987
67139
|
const integration = source ? TRAFFIC_SOURCE_INTEGRATIONS[source.sourceType] : void 0;
|
|
66988
67140
|
return integration ? { integration, action: "select" } : void 0;
|
|
66989
67141
|
}, async (request) => {
|
|
66990
67142
|
const project = resolveProject(app.db, request.params.name);
|
|
66991
67143
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
66992
67144
|
const result = app.db.transaction((tx) => {
|
|
66993
|
-
const target = tx.select().from(trafficSources).where(
|
|
67145
|
+
const target = tx.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
|
|
66994
67146
|
if (!target || target.projectId !== project.id || target.status === TrafficSourceStatuses.archived) {
|
|
66995
67147
|
throw notFound("Traffic source", request.params.id);
|
|
66996
67148
|
}
|
|
66997
67149
|
const { usesPullSchedule } = validateTrafficSourceCredential(target, project.name);
|
|
66998
|
-
const siblings = tx.select().from(trafficSources).where(
|
|
67150
|
+
const siblings = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all();
|
|
66999
67151
|
for (const sibling of siblings) {
|
|
67000
67152
|
if (sibling.id === target.id || sibling.status === TrafficSourceStatuses.archived) continue;
|
|
67001
67153
|
tx.update(trafficSources).set({
|
|
67002
67154
|
status: TrafficSourceStatuses.paused,
|
|
67003
67155
|
updatedAt: now
|
|
67004
|
-
}).where(
|
|
67156
|
+
}).where(eq91(trafficSources.id, sibling.id)).run();
|
|
67005
67157
|
}
|
|
67006
67158
|
tx.update(trafficSources).set({
|
|
67007
67159
|
status: TrafficSourceStatuses.connected,
|
|
67008
67160
|
lastError: null,
|
|
67009
67161
|
archivedAt: null,
|
|
67010
67162
|
updatedAt: now
|
|
67011
|
-
}).where(
|
|
67163
|
+
}).where(eq91(trafficSources.id, target.id)).run();
|
|
67012
67164
|
let scheduleAction = null;
|
|
67013
67165
|
if (usesPullSchedule) {
|
|
67014
67166
|
bindTrafficSyncSchedule(tx, project.id, target.id, now);
|
|
67015
67167
|
scheduleAction = "upsert";
|
|
67016
67168
|
} else {
|
|
67017
|
-
const schedule = tx.select().from(schedules).where(
|
|
67018
|
-
|
|
67019
|
-
|
|
67169
|
+
const schedule = tx.select().from(schedules).where(and80(
|
|
67170
|
+
eq91(schedules.projectId, project.id),
|
|
67171
|
+
eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
67020
67172
|
)).get();
|
|
67021
67173
|
if (schedule) {
|
|
67022
|
-
tx.delete(schedules).where(
|
|
67174
|
+
tx.delete(schedules).where(eq91(schedules.id, schedule.id)).run();
|
|
67023
67175
|
scheduleAction = "delete";
|
|
67024
67176
|
}
|
|
67025
67177
|
}
|
|
67026
|
-
const source = tx.select().from(trafficSources).where(
|
|
67178
|
+
const source = tx.select().from(trafficSources).where(eq91(trafficSources.id, target.id)).get();
|
|
67027
67179
|
writeAuditLog(tx, {
|
|
67028
67180
|
projectId: project.id,
|
|
67029
67181
|
actor: "api",
|
|
@@ -67091,7 +67243,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67091
67243
|
}
|
|
67092
67244
|
const { bearerHash, credential } = authenticated;
|
|
67093
67245
|
const sourceId = credential.sourceId;
|
|
67094
|
-
const sourceRow2 = app.db.select().from(trafficSources).where(
|
|
67246
|
+
const sourceRow2 = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get();
|
|
67095
67247
|
if (!sourceRow2 || sourceRow2.sourceType !== TrafficSourceTypes.cloudflare || sourceRow2.status !== TrafficSourceStatuses.connected || !parseDirectPushCloudflareSourceConfig(sourceRow2.configJson) || !timingSafeEqualHex(bearerHash, sourceRow2.ingestTokenHash)) {
|
|
67096
67248
|
throw authRequired();
|
|
67097
67249
|
}
|
|
@@ -67169,7 +67321,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67169
67321
|
});
|
|
67170
67322
|
app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
|
|
67171
67323
|
const project = resolveProject(app.db, request.params.name);
|
|
67172
|
-
const initialSourceRow = app.db.select().from(trafficSources).where(
|
|
67324
|
+
const initialSourceRow = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
|
|
67173
67325
|
if (!initialSourceRow || initialSourceRow.projectId !== project.id) {
|
|
67174
67326
|
throw notFound("Traffic source", request.params.id);
|
|
67175
67327
|
}
|
|
@@ -67242,7 +67394,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67242
67394
|
});
|
|
67243
67395
|
const finishSync = (status, error, stopReason) => {
|
|
67244
67396
|
const queueDrain = drainSummary(stopReason);
|
|
67245
|
-
app.db.update(runs).set({ status, finishedAt: committedAt, syncSummary: queueDrain, ...error ? { error } : {} }).where(
|
|
67397
|
+
app.db.update(runs).set({ status, finishedAt: committedAt, syncSummary: queueDrain, ...error ? { error } : {} }).where(eq91(runs.id, runId)).run();
|
|
67246
67398
|
writeAuditLog(app.db, {
|
|
67247
67399
|
projectId: project.id,
|
|
67248
67400
|
actor: "api",
|
|
@@ -67417,7 +67569,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67417
67569
|
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
67418
67570
|
ttlMs: CLOUDFLARE_QUEUE_SYNC_LEASE_TTL_MS
|
|
67419
67571
|
})) throw new Error("Queue sync lease was lost before acknowledgement");
|
|
67420
|
-
const latestSource = app.db.select().from(trafficSources).where(
|
|
67572
|
+
const latestSource = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
67421
67573
|
if (!latestSource || latestSource.projectId !== project.id || latestSource.status !== TrafficSourceStatuses.connected || latestSource.syncLeaseOwner !== leaseOwner || !isDeepStrictEqual(latestSource.configJson, sourceRow2.configJson)) {
|
|
67422
67574
|
throw validationError("Cloudflare Queue source was reconfigured before acknowledgement");
|
|
67423
67575
|
}
|
|
@@ -67506,13 +67658,13 @@ async function trafficRoutes(app, opts) {
|
|
|
67506
67658
|
error: safeError,
|
|
67507
67659
|
finishedAt: failedAt,
|
|
67508
67660
|
syncSummary: drainSummary(CloudflareQueueDrainStopReasons.error)
|
|
67509
|
-
}).where(
|
|
67510
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
67661
|
+
}).where(eq91(runs.id, runId)).run();
|
|
67662
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
67511
67663
|
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
|
-
|
|
67664
|
+
tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and80(
|
|
67665
|
+
eq91(trafficSources.id, sourceRow2.id),
|
|
67666
|
+
eq91(trafficSources.status, TrafficSourceStatuses.connected),
|
|
67667
|
+
eq91(trafficSources.syncLeaseOwner, leaseOwner)
|
|
67516
67668
|
)).run();
|
|
67517
67669
|
}
|
|
67518
67670
|
});
|
|
@@ -67531,7 +67683,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67531
67683
|
`Sync for source type "${sourceRow2.sourceType}" is not implemented yet \u2014 only cloud-run, wordpress, and vercel are supported in v1.`
|
|
67532
67684
|
);
|
|
67533
67685
|
}
|
|
67534
|
-
const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(
|
|
67686
|
+
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
67687
|
if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
|
|
67536
67688
|
throw validationError("Traffic source must be active before it can sync");
|
|
67537
67689
|
}
|
|
@@ -67568,10 +67720,10 @@ async function trafficRoutes(app, opts) {
|
|
|
67568
67720
|
const markFailed = (msg, errorCode2) => {
|
|
67569
67721
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
67570
67722
|
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(
|
|
67723
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq91(runs.id, runId)).run();
|
|
67724
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
67573
67725
|
if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && latestSource.syncLeaseOwner === syncLeaseOwner) {
|
|
67574
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
67726
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
67575
67727
|
}
|
|
67576
67728
|
});
|
|
67577
67729
|
try {
|
|
@@ -67665,7 +67817,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67665
67817
|
}
|
|
67666
67818
|
const credential = credentialStore.getConnection(project.name);
|
|
67667
67819
|
if (!credential) {
|
|
67668
|
-
app.db.delete(runs).where(
|
|
67820
|
+
app.db.delete(runs).where(eq91(runs.id, runId)).run();
|
|
67669
67821
|
throw validationError(
|
|
67670
67822
|
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
67671
67823
|
);
|
|
@@ -67699,14 +67851,14 @@ async function trafficRoutes(app, opts) {
|
|
|
67699
67851
|
const windowEndIso2 = windowEnd.toISOString();
|
|
67700
67852
|
const reservedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
67701
67853
|
const reservedSource = app.db.transaction((tx) => {
|
|
67702
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
67854
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
67703
67855
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== syncLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
|
|
67704
67856
|
tx.update(trafficSources).set({
|
|
67705
67857
|
lastSyncedAt: windowStartIso2,
|
|
67706
67858
|
wordpressPendingUntil: windowEndIso2,
|
|
67707
67859
|
updatedAt: reservedAt
|
|
67708
|
-
}).where(
|
|
67709
|
-
return tx.select().from(trafficSources).where(
|
|
67860
|
+
}).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
67861
|
+
return tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
67710
67862
|
}, { behavior: "immediate" });
|
|
67711
67863
|
if (!reservedSource) {
|
|
67712
67864
|
const message = "Traffic source changed while reserving the WordPress sync window; retry the sync.";
|
|
@@ -67766,12 +67918,12 @@ async function trafficRoutes(app, opts) {
|
|
|
67766
67918
|
auditAction = "traffic.vercel.synced";
|
|
67767
67919
|
const credentialStore = opts.vercelTrafficCredentialStore;
|
|
67768
67920
|
if (!credentialStore) {
|
|
67769
|
-
app.db.delete(runs).where(
|
|
67921
|
+
app.db.delete(runs).where(eq91(runs.id, runId)).run();
|
|
67770
67922
|
throw validationError("Vercel traffic credential storage is not configured for this deployment");
|
|
67771
67923
|
}
|
|
67772
67924
|
const credential = credentialStore.getConnection(project.name);
|
|
67773
67925
|
if (!credential) {
|
|
67774
|
-
app.db.delete(runs).where(
|
|
67926
|
+
app.db.delete(runs).where(eq91(runs.id, runId)).run();
|
|
67775
67927
|
throw validationError(
|
|
67776
67928
|
`No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
|
|
67777
67929
|
);
|
|
@@ -67789,7 +67941,7 @@ async function trafficRoutes(app, opts) {
|
|
|
67789
67941
|
if (cappedStartMs > clampedStartMs) {
|
|
67790
67942
|
const previousSkip = sourceRow2.skippedThroughAt ? Date.parse(sourceRow2.skippedThroughAt) : Number.NaN;
|
|
67791
67943
|
const skippedThrough = Number.isFinite(previousSkip) ? new Date(Math.max(previousSkip, cappedStartMs)) : new Date(cappedStartMs);
|
|
67792
|
-
app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(
|
|
67944
|
+
app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
67793
67945
|
request.log.warn(
|
|
67794
67946
|
{
|
|
67795
67947
|
sourceId: sourceRow2.id,
|
|
@@ -67883,15 +68035,15 @@ async function trafficRoutes(app, opts) {
|
|
|
67883
68035
|
throw providerError(`WordPress sync failed: ${msg}`);
|
|
67884
68036
|
}
|
|
67885
68037
|
const commitOutcome = app.db.transaction((tx) => {
|
|
67886
|
-
const latestRow = tx.select().from(trafficSources).where(
|
|
67887
|
-
const latestHasConnectedSibling = tx.select().from(trafficSources).where(
|
|
68038
|
+
const latestRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
68039
|
+
const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
|
|
67888
68040
|
if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || latestRow.syncLeaseOwner !== syncLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
|
|
67889
68041
|
const abortedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
67890
68042
|
tx.update(runs).set({
|
|
67891
68043
|
status: RunStatuses.failed,
|
|
67892
68044
|
error: "Traffic source was deactivated or reconfigured during sync",
|
|
67893
68045
|
finishedAt: abortedAt
|
|
67894
|
-
}).where(
|
|
68046
|
+
}).where(eq91(runs.id, runId)).run();
|
|
67895
68047
|
return "source-inactive";
|
|
67896
68048
|
}
|
|
67897
68049
|
const previousIds = latestRow.lastEventIds ?? [];
|
|
@@ -68177,8 +68329,8 @@ async function trafficRoutes(app, opts) {
|
|
|
68177
68329
|
sourceUpdate.lastCursor = nextCursor ?? null;
|
|
68178
68330
|
sourceUpdate.wordpressPendingUntil = nextCursor ? windowEnd.toISOString() : null;
|
|
68179
68331
|
}
|
|
68180
|
-
tx.update(trafficSources).set(sourceUpdate).where(
|
|
68181
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
68332
|
+
tx.update(trafficSources).set(sourceUpdate).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
68333
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq91(runs.id, runId)).run();
|
|
68182
68334
|
return "committed";
|
|
68183
68335
|
}, { behavior: "immediate" });
|
|
68184
68336
|
if (commitOutcome === "source-inactive") {
|
|
@@ -68247,7 +68399,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68247
68399
|
});
|
|
68248
68400
|
app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => withFeatureOutcome(app, { feature: "server_traffic", operation: "backfill" }, async (recordOutcome) => {
|
|
68249
68401
|
const project = resolveProject(app.db, request.params.name);
|
|
68250
|
-
const sourceRow2 = app.db.select().from(trafficSources).where(
|
|
68402
|
+
const sourceRow2 = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
|
|
68251
68403
|
if (!sourceRow2 || sourceRow2.projectId !== project.id) {
|
|
68252
68404
|
throw notFound("Traffic source", request.params.id);
|
|
68253
68405
|
}
|
|
@@ -68372,7 +68524,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68372
68524
|
}
|
|
68373
68525
|
try {
|
|
68374
68526
|
app.db.transaction((tx) => {
|
|
68375
|
-
const latestSource = tx.select().from(trafficSources).where(
|
|
68527
|
+
const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
68376
68528
|
if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
|
|
68377
68529
|
throw validationError("Traffic source must remain active and unchanged before it can backfill");
|
|
68378
68530
|
}
|
|
@@ -68422,8 +68574,8 @@ async function trafficRoutes(app, opts) {
|
|
|
68422
68574
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
68423
68575
|
hits: sql40`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
68424
68576
|
}).from(crawlerEventsHourly).where(
|
|
68425
|
-
|
|
68426
|
-
|
|
68577
|
+
and80(
|
|
68578
|
+
eq91(crawlerEventsHourly.sourceId, row.id),
|
|
68427
68579
|
gte25(crawlerEventsHourly.tsHour, since)
|
|
68428
68580
|
)
|
|
68429
68581
|
).groupBy(crawlerEventsHourly.pathNormalized).all();
|
|
@@ -68432,14 +68584,14 @@ async function trafficRoutes(app, opts) {
|
|
|
68432
68584
|
);
|
|
68433
68585
|
const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
|
|
68434
68586
|
const aiUserFetchTotals = app.db.select({ total: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
68435
|
-
|
|
68436
|
-
|
|
68587
|
+
and80(
|
|
68588
|
+
eq91(aiUserFetchEventsHourly.sourceId, row.id),
|
|
68437
68589
|
gte25(aiUserFetchEventsHourly.tsHour, since)
|
|
68438
68590
|
)
|
|
68439
68591
|
).get();
|
|
68440
68592
|
const likelyFakeTotals = app.db.select({ total: sql40`COALESCE(SUM(${aiUserFetchLikelyFakeHourly.hits}), 0)` }).from(aiUserFetchLikelyFakeHourly).where(
|
|
68441
|
-
|
|
68442
|
-
|
|
68593
|
+
and80(
|
|
68594
|
+
eq91(aiUserFetchLikelyFakeHourly.sourceId, row.id),
|
|
68443
68595
|
gte25(aiUserFetchLikelyFakeHourly.tsHour, since)
|
|
68444
68596
|
)
|
|
68445
68597
|
).get();
|
|
@@ -68448,22 +68600,22 @@ async function trafficRoutes(app, opts) {
|
|
|
68448
68600
|
nonhop: sql40`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
|
|
68449
68601
|
landed: sql40`COALESCE(SUM(CASE WHEN ${countableReferralCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`
|
|
68450
68602
|
}).from(aiReferralEventsHourly).where(
|
|
68451
|
-
|
|
68452
|
-
|
|
68603
|
+
and80(
|
|
68604
|
+
eq91(aiReferralEventsHourly.sourceId, row.id),
|
|
68453
68605
|
gte25(aiReferralEventsHourly.tsHour, since)
|
|
68454
68606
|
)
|
|
68455
68607
|
).get();
|
|
68456
68608
|
const sampleTotals = app.db.select({ total: sql40`COUNT(*)` }).from(rawEventSamples).where(
|
|
68457
|
-
|
|
68458
|
-
|
|
68609
|
+
and80(
|
|
68610
|
+
eq91(rawEventSamples.sourceId, row.id),
|
|
68459
68611
|
gte25(rawEventSamples.ts, since)
|
|
68460
68612
|
)
|
|
68461
68613
|
).get();
|
|
68462
68614
|
const latestRun = app.db.select().from(runs).where(
|
|
68463
|
-
|
|
68464
|
-
|
|
68465
|
-
|
|
68466
|
-
|
|
68615
|
+
and80(
|
|
68616
|
+
eq91(runs.projectId, projectId),
|
|
68617
|
+
eq91(runs.kind, RunKinds["traffic-sync"]),
|
|
68618
|
+
eq91(runs.sourceId, row.id)
|
|
68467
68619
|
)
|
|
68468
68620
|
).orderBy(desc41(runs.startedAt)).limit(1).get();
|
|
68469
68621
|
return {
|
|
@@ -68501,7 +68653,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68501
68653
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
68502
68654
|
let updatedRow;
|
|
68503
68655
|
app.db.transaction((tx) => {
|
|
68504
|
-
const sourceRow2 = tx.select().from(trafficSources).where(
|
|
68656
|
+
const sourceRow2 = tx.select().from(trafficSources).where(and80(eq91(trafficSources.projectId, project.id), eq91(trafficSources.id, request.params.id))).get();
|
|
68505
68657
|
if (!sourceRow2) {
|
|
68506
68658
|
throw notFound("traffic source", request.params.id);
|
|
68507
68659
|
}
|
|
@@ -68515,7 +68667,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68515
68667
|
`Traffic source "${sourceRow2.id}" is staged. Activate it explicitly instead of resetting it.`
|
|
68516
68668
|
);
|
|
68517
68669
|
}
|
|
68518
|
-
const hasConnectedSibling = tx.select().from(trafficSources).where(
|
|
68670
|
+
const hasConnectedSibling = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
|
|
68519
68671
|
if (hasConnectedSibling) {
|
|
68520
68672
|
throw validationError(
|
|
68521
68673
|
`Traffic source "${sourceRow2.id}" is not authoritative. Activate it explicitly instead of resetting it.`
|
|
@@ -68532,7 +68684,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68532
68684
|
status: TrafficSourceStatuses.connected,
|
|
68533
68685
|
lastError: null,
|
|
68534
68686
|
updatedAt: now
|
|
68535
|
-
}).where(
|
|
68687
|
+
}).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
68536
68688
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
68537
68689
|
projectId: project.id,
|
|
68538
68690
|
actor: "api",
|
|
@@ -68540,7 +68692,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68540
68692
|
entityType: "traffic_source",
|
|
68541
68693
|
entityId: sourceRow2.id
|
|
68542
68694
|
}));
|
|
68543
|
-
updatedRow = tx.select().from(trafficSources).where(
|
|
68695
|
+
updatedRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
68544
68696
|
});
|
|
68545
68697
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
68546
68698
|
}));
|
|
@@ -68552,7 +68704,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68552
68704
|
}
|
|
68553
68705
|
let updatedRow;
|
|
68554
68706
|
app.db.transaction((tx) => {
|
|
68555
|
-
const sourceRow2 = tx.select().from(trafficSources).where(
|
|
68707
|
+
const sourceRow2 = tx.select().from(trafficSources).where(and80(eq91(trafficSources.projectId, project.id), eq91(trafficSources.id, request.params.id))).get();
|
|
68556
68708
|
if (!sourceRow2) {
|
|
68557
68709
|
throw notFound("traffic source", request.params.id);
|
|
68558
68710
|
}
|
|
@@ -68565,7 +68717,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68565
68717
|
);
|
|
68566
68718
|
}
|
|
68567
68719
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
68568
|
-
tx.update(trafficSources).set({ skippedThroughAt: null, updatedAt: now }).where(
|
|
68720
|
+
tx.update(trafficSources).set({ skippedThroughAt: null, updatedAt: now }).where(eq91(trafficSources.id, sourceRow2.id)).run();
|
|
68569
68721
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
68570
68722
|
projectId: project.id,
|
|
68571
68723
|
actor: "api",
|
|
@@ -68574,20 +68726,20 @@ async function trafficRoutes(app, opts) {
|
|
|
68574
68726
|
entityId: sourceRow2.id,
|
|
68575
68727
|
diff: { skippedThroughAt: sourceRow2.skippedThroughAt, sourceType: sourceRow2.sourceType }
|
|
68576
68728
|
}));
|
|
68577
|
-
updatedRow = tx.select().from(trafficSources).where(
|
|
68729
|
+
updatedRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
|
|
68578
68730
|
});
|
|
68579
68731
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
68580
68732
|
});
|
|
68581
68733
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
68582
68734
|
const project = resolveProject(app.db, request.params.name);
|
|
68583
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
68735
|
+
const rows = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
|
|
68584
68736
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
|
|
68585
68737
|
const response = { sources };
|
|
68586
68738
|
return response;
|
|
68587
68739
|
});
|
|
68588
68740
|
app.get("/projects/:name/traffic/status", async (request) => {
|
|
68589
68741
|
const project = resolveProject(app.db, request.params.name);
|
|
68590
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
68742
|
+
const rows = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
|
|
68591
68743
|
const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
|
|
68592
68744
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
|
|
68593
68745
|
const response = { sources };
|
|
@@ -68597,7 +68749,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68597
68749
|
"/projects/:name/traffic/sources/:id",
|
|
68598
68750
|
async (request) => {
|
|
68599
68751
|
const project = resolveProject(app.db, request.params.name);
|
|
68600
|
-
const row = app.db.select().from(trafficSources).where(
|
|
68752
|
+
const row = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
|
|
68601
68753
|
if (!row || row.projectId !== project.id) {
|
|
68602
68754
|
throw notFound("Traffic source", request.params.id);
|
|
68603
68755
|
}
|
|
@@ -68665,12 +68817,12 @@ async function trafficRoutes(app, opts) {
|
|
|
68665
68817
|
const seriesByBucket = /* @__PURE__ */ new Map();
|
|
68666
68818
|
if (kinds.has(TrafficEventKinds.crawler)) {
|
|
68667
68819
|
const crawlerFilters = [
|
|
68668
|
-
|
|
68820
|
+
eq91(crawlerEventsHourly.projectId, project.id),
|
|
68669
68821
|
gte25(crawlerEventsHourly.tsHour, sinceIso),
|
|
68670
68822
|
lte26(crawlerEventsHourly.tsHour, untilIso)
|
|
68671
68823
|
];
|
|
68672
|
-
if (sourceIdParam) crawlerFilters.push(
|
|
68673
|
-
const crawlerWhere =
|
|
68824
|
+
if (sourceIdParam) crawlerFilters.push(eq91(crawlerEventsHourly.sourceId, sourceIdParam));
|
|
68825
|
+
const crawlerWhere = and80(...crawlerFilters);
|
|
68674
68826
|
const pathTotals = app.db.select({
|
|
68675
68827
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
68676
68828
|
hits: sql40`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
|
|
@@ -68740,12 +68892,12 @@ async function trafficRoutes(app, opts) {
|
|
|
68740
68892
|
}
|
|
68741
68893
|
if (kinds.has(TrafficEventKinds["ai-user-fetch"])) {
|
|
68742
68894
|
const userFetchFilters = [
|
|
68743
|
-
|
|
68895
|
+
eq91(aiUserFetchEventsHourly.projectId, project.id),
|
|
68744
68896
|
gte25(aiUserFetchEventsHourly.tsHour, sinceIso),
|
|
68745
68897
|
lte26(aiUserFetchEventsHourly.tsHour, untilIso)
|
|
68746
68898
|
];
|
|
68747
|
-
if (sourceIdParam) userFetchFilters.push(
|
|
68748
|
-
const userFetchWhere =
|
|
68899
|
+
if (sourceIdParam) userFetchFilters.push(eq91(aiUserFetchEventsHourly.sourceId, sourceIdParam));
|
|
68900
|
+
const userFetchWhere = and80(...userFetchFilters);
|
|
68749
68901
|
const total = app.db.select({
|
|
68750
68902
|
total: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
|
|
68751
68903
|
rows: sql40`COUNT(*)`
|
|
@@ -68779,12 +68931,12 @@ async function trafficRoutes(app, opts) {
|
|
|
68779
68931
|
}
|
|
68780
68932
|
if (kinds.has(TrafficEventKinds["ai-referral"])) {
|
|
68781
68933
|
const aiFilters = [
|
|
68782
|
-
|
|
68934
|
+
eq91(aiReferralEventsHourly.projectId, project.id),
|
|
68783
68935
|
gte25(aiReferralEventsHourly.tsHour, sinceIso),
|
|
68784
68936
|
lte26(aiReferralEventsHourly.tsHour, untilIso)
|
|
68785
68937
|
];
|
|
68786
|
-
if (sourceIdParam) aiFilters.push(
|
|
68787
|
-
const aiWhere =
|
|
68938
|
+
if (sourceIdParam) aiFilters.push(eq91(aiReferralEventsHourly.sourceId, sourceIdParam));
|
|
68939
|
+
const aiWhere = and80(...aiFilters);
|
|
68788
68940
|
const total = app.db.select({
|
|
68789
68941
|
total: sql40`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
|
|
68790
68942
|
nonhop: sql40`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
|
|
@@ -68859,7 +69011,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68859
69011
|
windowEnd: untilIso,
|
|
68860
69012
|
series: (() => {
|
|
68861
69013
|
const points = completeTrafficSeries(since, until, granularity, seriesByBucket);
|
|
68862
|
-
const coverageScope = (col, srcCol) => sourceIdParam ?
|
|
69014
|
+
const coverageScope = (col, srcCol) => sourceIdParam ? and80(eq91(col, project.id), eq91(srcCol, sourceIdParam)) : eq91(col, project.id);
|
|
68863
69015
|
const firsts = [
|
|
68864
69016
|
app.db.select({ v: sql40`MIN(${crawlerEventsHourly.tsHour})` }).from(crawlerEventsHourly).where(coverageScope(crawlerEventsHourly.projectId, crawlerEventsHourly.sourceId)).get()?.v,
|
|
68865
69017
|
app.db.select({ v: sql40`MIN(${aiUserFetchEventsHourly.tsHour})` }).from(aiUserFetchEventsHourly).where(coverageScope(aiUserFetchEventsHourly.projectId, aiUserFetchEventsHourly.sourceId)).get()?.v,
|
|
@@ -68946,7 +69098,7 @@ async function trafficRoutes(app, opts) {
|
|
|
68946
69098
|
}
|
|
68947
69099
|
|
|
68948
69100
|
// ../api-routes/src/doctor/checks/report-sweeps.ts
|
|
68949
|
-
import { and as
|
|
69101
|
+
import { and as and81, asc as asc24, eq as eq92, gte as gte26, lte as lte27 } from "drizzle-orm";
|
|
68950
69102
|
|
|
68951
69103
|
// ../api-routes/src/visibility-attribution.ts
|
|
68952
69104
|
function buildQueryAttribution2(projectQueries) {
|
|
@@ -68980,11 +69132,11 @@ function inspectSweep(ctx, run) {
|
|
|
68980
69132
|
answerMentioned: querySnapshots.answerMentioned,
|
|
68981
69133
|
answerText: querySnapshots.answerText,
|
|
68982
69134
|
measurementExecutionId: querySnapshots.measurementExecutionId
|
|
68983
|
-
}).from(querySnapshots).where(
|
|
69135
|
+
}).from(querySnapshots).where(eq92(querySnapshots.runId, run.id)).all();
|
|
68984
69136
|
if (snapshots.length === 0) return { detail: { ...base, complete: false, reason: "no-answers" } };
|
|
68985
69137
|
try {
|
|
68986
69138
|
if (run.measurementPlanVersionId) {
|
|
68987
|
-
const version = ctx.db.select().from(measurementPlanVersions).where(
|
|
69139
|
+
const version = ctx.db.select().from(measurementPlanVersions).where(and81(eq92(measurementPlanVersions.projectId, ctx.project.id), eq92(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
|
|
68988
69140
|
if (!version) return { detail: { ...base, complete: false, reason: "missing-plan" } };
|
|
68989
69141
|
const plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
|
|
68990
69142
|
const manifest = measurementRunExpectedSlots(run, plan);
|
|
@@ -69016,10 +69168,10 @@ function inspectSweep(ctx, run) {
|
|
|
69016
69168
|
};
|
|
69017
69169
|
return unbound ? { detail } : { detail, coverage: slotCoverage(expected2, answered3) };
|
|
69018
69170
|
}
|
|
69019
|
-
const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(
|
|
69171
|
+
const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq92(simpleMeasurementDefinitions.runId, run.id)).get();
|
|
69020
69172
|
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(
|
|
69173
|
+
const project = ctx.db.select().from(projects).where(eq92(projects.id, ctx.project.id)).get();
|
|
69174
|
+
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
69175
|
const providers = frozen ? frozen.engines.map((engine) => engine.provider) : project.providers.length > 0 ? project.providers : [...new Set(snapshots.map((snapshot) => snapshot.provider))];
|
|
69024
69176
|
const attribution = buildQueryAttribution2(basket);
|
|
69025
69177
|
const expected = new Set(basket.flatMap((query2) => providers.map((provider) => JSON.stringify([query2.id, provider.trim().toLowerCase()]))));
|
|
@@ -69068,7 +69220,7 @@ var reportSweepsCheck = {
|
|
|
69068
69220
|
const graded = gradedReportMonths(reportMonths, now);
|
|
69069
69221
|
const months = reportMonths.map((month) => {
|
|
69070
69222
|
const bounds = calendarMonthBounds(month);
|
|
69071
|
-
const monthRuns = ctx.db.select().from(runs).where(
|
|
69223
|
+
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
69224
|
const evidence = monthRuns.map((run) => inspectSweep(ctx, run));
|
|
69073
69225
|
return {
|
|
69074
69226
|
month,
|
|
@@ -69091,7 +69243,7 @@ var reportSweepsCheck = {
|
|
|
69091
69243
|
};
|
|
69092
69244
|
|
|
69093
69245
|
// ../api-routes/src/doctor/checks/report-models.ts
|
|
69094
|
-
import { and as
|
|
69246
|
+
import { and as and82, eq as eq93, gte as gte27, inArray as inArray42, lte as lte28 } from "drizzle-orm";
|
|
69095
69247
|
var reportModelsCheck = {
|
|
69096
69248
|
id: "report.models",
|
|
69097
69249
|
category: CheckCategories.providers,
|
|
@@ -69106,7 +69258,7 @@ var reportModelsCheck = {
|
|
|
69106
69258
|
const months = reportMonths.flatMap((month) => {
|
|
69107
69259
|
const bounds = calendarMonthBounds(month);
|
|
69108
69260
|
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,
|
|
69261
|
+
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
69262
|
return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
|
|
69111
69263
|
month,
|
|
69112
69264
|
previousMonth: previous,
|
|
@@ -69147,7 +69299,7 @@ var reportModelsCheck = {
|
|
|
69147
69299
|
};
|
|
69148
69300
|
|
|
69149
69301
|
// ../api-routes/src/doctor/checks/report-daily-data.ts
|
|
69150
|
-
import { eq as
|
|
69302
|
+
import { eq as eq94 } from "drizzle-orm";
|
|
69151
69303
|
var REPORTING_LAG_DAYS = 3;
|
|
69152
69304
|
function dateSpan(dates) {
|
|
69153
69305
|
const sorted = [...dates].sort();
|
|
@@ -69156,31 +69308,31 @@ function dateSpan(dates) {
|
|
|
69156
69308
|
function latestSyncRange(ctx, source) {
|
|
69157
69309
|
const projectId = ctx.project.id;
|
|
69158
69310
|
if (source === "ga") {
|
|
69159
|
-
const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(
|
|
69311
|
+
const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq94(gaDailyTotals.projectId, projectId)).all();
|
|
69160
69312
|
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(
|
|
69313
|
+
const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq94(gaTrafficSummaries.projectId, projectId)).get();
|
|
69162
69314
|
if (summary3 && (latest2 === null || summary3.syncedAt >= latest2)) return { start: summary3.start, end: summary3.end, syncedAt: summary3.syncedAt };
|
|
69163
69315
|
const span2 = dateSpan(rows2.filter((row) => row.syncedAt === latest2).map((row) => row.date));
|
|
69164
69316
|
return span2 && latest2 !== null ? { ...span2, syncedAt: latest2 } : null;
|
|
69165
69317
|
}
|
|
69166
|
-
const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(
|
|
69318
|
+
const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq94(gscDailyTotals.projectId, projectId)).all();
|
|
69167
69319
|
const latest = rows.reduce((max2, row) => max2 === null || row.createdAt > max2 ? row.createdAt : max2, null);
|
|
69168
69320
|
const span = dateSpan(rows.filter((row) => row.createdAt === latest).map((row) => row.date));
|
|
69169
69321
|
if (!span || latest === null) return null;
|
|
69170
|
-
const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(
|
|
69322
|
+
const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq94(gscDataWatermarks.projectId, projectId)).get();
|
|
69171
69323
|
return watermark?.through && watermark.updatedAt === latest && watermark.through > span.end ? { start: span.start, end: watermark.through, syncedAt: latest } : { ...span, syncedAt: latest };
|
|
69172
69324
|
}
|
|
69173
69325
|
function dailyCoverage(ctx, month, source) {
|
|
69174
69326
|
const project = ctx.project;
|
|
69175
69327
|
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(
|
|
69328
|
+
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
69329
|
if (!connection && stored.length === 0) return null;
|
|
69178
69330
|
const bounds = calendarMonthBounds(month);
|
|
69179
69331
|
const start = bounds.since.slice(0, 10);
|
|
69180
69332
|
const end = bounds.until.slice(0, 10);
|
|
69181
69333
|
const timeZone = source === "gsc" ? "America/Los_Angeles" : "UTC";
|
|
69182
69334
|
const matureThrough = isoDateDaysBeforeInTimeZone((/* @__PURE__ */ new Date()).toISOString(), REPORTING_LAG_DAYS, timeZone);
|
|
69183
|
-
const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(
|
|
69335
|
+
const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq94(projects.id, project.id)).get().date.slice(0, 10);
|
|
69184
69336
|
const connectedAt = connection?.createdAt?.slice(0, 10) ?? created;
|
|
69185
69337
|
const onboardingDate = [connectedAt, ...stored.map((row) => row.date)].sort()[0];
|
|
69186
69338
|
const byDate = new Map(stored.map((row) => [row.date, row.count]));
|
|
@@ -69262,7 +69414,7 @@ var reportDailyDataCheck = {
|
|
|
69262
69414
|
};
|
|
69263
69415
|
|
|
69264
69416
|
// ../api-routes/src/doctor/checks/referral-assessment.ts
|
|
69265
|
-
import { and as
|
|
69417
|
+
import { and as and83, eq as eq95, ne as ne16 } from "drizzle-orm";
|
|
69266
69418
|
var REFERRAL_ASSESSMENT_CHECKS = [{
|
|
69267
69419
|
id: "report.ai-referral-bursts",
|
|
69268
69420
|
category: CheckCategories.integrations,
|
|
@@ -69271,7 +69423,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
|
|
|
69271
69423
|
notificationPolicy: CheckNotificationPolicies.silent,
|
|
69272
69424
|
run: (ctx) => {
|
|
69273
69425
|
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(
|
|
69426
|
+
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
69427
|
if (!configured) {
|
|
69276
69428
|
return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.not-configured", summary: "No non-archived server traffic source is configured for this project." };
|
|
69277
69429
|
}
|
|
@@ -69683,17 +69835,17 @@ var BACKLINKS_CHECKS = [
|
|
|
69683
69835
|
];
|
|
69684
69836
|
|
|
69685
69837
|
// ../api-routes/src/doctor/checks/bing-auth.ts
|
|
69686
|
-
import { and as
|
|
69838
|
+
import { and as and84, desc as desc42, eq as eq96, inArray as inArray43 } from "drizzle-orm";
|
|
69687
69839
|
var BING_CONNECTION_ENTITY = "bing_connection";
|
|
69688
69840
|
var BING_CONNECTED_ACTION = "bing.connected";
|
|
69689
69841
|
var BING_DISCONNECTED_ACTION = "bing.disconnected";
|
|
69690
69842
|
function lostBingConnection(ctx, project) {
|
|
69691
69843
|
const hasRow = (row) => row !== void 0;
|
|
69692
|
-
const storedData = hasRow(ctx.db.select({ id: bingCoverageSnapshots.id }).from(bingCoverageSnapshots).where(
|
|
69844
|
+
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
69845
|
if (!storedData) return false;
|
|
69694
|
-
const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(
|
|
69695
|
-
|
|
69696
|
-
|
|
69846
|
+
const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and84(
|
|
69847
|
+
eq96(auditLog.entityType, BING_CONNECTION_ENTITY),
|
|
69848
|
+
eq96(auditLog.entityId, project.canonicalDomain),
|
|
69697
69849
|
inArray43(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
|
|
69698
69850
|
)).orderBy(desc42(auditLog.createdAt)).limit(1).get();
|
|
69699
69851
|
return lastChange?.action !== BING_DISCONNECTED_ACTION;
|
|
@@ -69852,7 +70004,7 @@ var BING_AUTH_CHECKS = [
|
|
|
69852
70004
|
];
|
|
69853
70005
|
|
|
69854
70006
|
// ../api-routes/src/doctor/checks/content.ts
|
|
69855
|
-
import { eq as
|
|
70007
|
+
import { eq as eq97 } from "drizzle-orm";
|
|
69856
70008
|
var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
|
|
69857
70009
|
var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
|
|
69858
70010
|
function skippedNoProject2() {
|
|
@@ -69865,7 +70017,7 @@ function skippedNoProject2() {
|
|
|
69865
70017
|
}
|
|
69866
70018
|
function loadProject(ctx) {
|
|
69867
70019
|
if (!ctx.project) return null;
|
|
69868
|
-
return ctx.db.select().from(projects).where(
|
|
70020
|
+
return ctx.db.select().from(projects).where(eq97(projects.id, ctx.project.id)).get() ?? null;
|
|
69869
70021
|
}
|
|
69870
70022
|
var winnabilityCoverageCheck = {
|
|
69871
70023
|
id: "content.winnability.coverage",
|
|
@@ -69958,7 +70110,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
|
|
|
69958
70110
|
);
|
|
69959
70111
|
|
|
69960
70112
|
// ../api-routes/src/doctor/checks/ads.ts
|
|
69961
|
-
import { eq as
|
|
70113
|
+
import { eq as eq98 } from "drizzle-orm";
|
|
69962
70114
|
var ADS_RECENT_SYNC_WARN_DAYS = 7;
|
|
69963
70115
|
var ADS_RECENT_SYNC_FAIL_DAYS = 30;
|
|
69964
70116
|
var adsConnectionCheck = {
|
|
@@ -69975,7 +70127,7 @@ var adsConnectionCheck = {
|
|
|
69975
70127
|
remediation: null
|
|
69976
70128
|
};
|
|
69977
70129
|
}
|
|
69978
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
70130
|
+
const row = ctx.db.select().from(adsConnections).where(eq98(adsConnections.projectId, ctx.project.id)).get();
|
|
69979
70131
|
if (!row) {
|
|
69980
70132
|
return {
|
|
69981
70133
|
status: CheckStatuses.skipped,
|
|
@@ -70025,7 +70177,7 @@ var adsRecentSyncCheck = {
|
|
|
70025
70177
|
remediation: null
|
|
70026
70178
|
};
|
|
70027
70179
|
}
|
|
70028
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
70180
|
+
const row = ctx.db.select().from(adsConnections).where(eq98(adsConnections.projectId, ctx.project.id)).get();
|
|
70029
70181
|
if (!row) {
|
|
70030
70182
|
return {
|
|
70031
70183
|
status: CheckStatuses.skipped,
|
|
@@ -70240,7 +70392,7 @@ var ga4ConnectionCheck = {
|
|
|
70240
70392
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
70241
70393
|
|
|
70242
70394
|
// ../api-routes/src/doctor/checks/data-freshness.ts
|
|
70243
|
-
import { and as
|
|
70395
|
+
import { and as and85, desc as desc43, eq as eq99, inArray as inArray44 } from "drizzle-orm";
|
|
70244
70396
|
var GA_DATA_AGING_DAYS = 3;
|
|
70245
70397
|
var GA_DATA_STALE_DAYS = 5;
|
|
70246
70398
|
var GSC_DATA_AGING_DAYS = 5;
|
|
@@ -70270,7 +70422,7 @@ function skipped(code, summary3) {
|
|
|
70270
70422
|
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
70271
70423
|
}
|
|
70272
70424
|
function lastSyncAgeDays(ctx, kinds) {
|
|
70273
|
-
const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(
|
|
70425
|
+
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
70426
|
if (!row?.createdAt) return null;
|
|
70275
70427
|
const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS6);
|
|
70276
70428
|
return Number.isFinite(age) ? age : null;
|
|
@@ -70372,7 +70524,7 @@ var gaRecentDataCheck = {
|
|
|
70372
70524
|
const connection = gaConnected(ctx);
|
|
70373
70525
|
if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
|
|
70374
70526
|
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(
|
|
70527
|
+
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
70528
|
return grade(ctx, GA_SPEC, newest?.date);
|
|
70377
70529
|
}
|
|
70378
70530
|
};
|
|
@@ -70387,16 +70539,16 @@ var gscRecentDataCheck = {
|
|
|
70387
70539
|
if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
|
|
70388
70540
|
return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
|
|
70389
70541
|
}
|
|
70390
|
-
const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(
|
|
70542
|
+
const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq99(gscDataWatermarks.projectId, ctx.project.id)).get();
|
|
70391
70543
|
if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
|
|
70392
|
-
const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(
|
|
70544
|
+
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
70545
|
return grade(ctx, GSC_SPEC, newest?.date);
|
|
70394
70546
|
}
|
|
70395
70547
|
};
|
|
70396
70548
|
var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
|
|
70397
70549
|
|
|
70398
70550
|
// ../api-routes/src/doctor/checks/seo-refresh.ts
|
|
70399
|
-
import { and as
|
|
70551
|
+
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
70552
|
var GRACE_DAYS = 2;
|
|
70401
70553
|
var DAY_MS7 = 24 * 60 * 60 * 1e3;
|
|
70402
70554
|
var FIRING_SPAN_MS = 6e4;
|
|
@@ -70405,7 +70557,7 @@ function skipped2(code, summary3) {
|
|
|
70405
70557
|
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
70406
70558
|
}
|
|
70407
70559
|
function seoSyncRuns(projectId) {
|
|
70408
|
-
return
|
|
70560
|
+
return and86(eq100(runs.projectId, projectId), eq100(runs.kind, RunKinds["seo-sync"]));
|
|
70409
70561
|
}
|
|
70410
70562
|
function scheduleTiming(schedule) {
|
|
70411
70563
|
if (!schedule.recurrence) return { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence: null };
|
|
@@ -70413,16 +70565,16 @@ function scheduleTiming(schedule) {
|
|
|
70413
70565
|
return recurrence ? { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence } : null;
|
|
70414
70566
|
}
|
|
70415
70567
|
function latestFiring(ctx, projectId, enabledSince) {
|
|
70416
|
-
const scheduled =
|
|
70568
|
+
const scheduled = and86(seoSyncRuns(projectId), eq100(runs.trigger, RunTriggers.scheduled), gt8(runs.createdAt, enabledSince));
|
|
70417
70569
|
const newest = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(scheduled).orderBy(desc44(runs.createdAt)).limit(1).get();
|
|
70418
70570
|
if (!newest) return [];
|
|
70419
70571
|
const newestMs = Date.parse(newest.createdAt);
|
|
70420
70572
|
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(
|
|
70573
|
+
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
70574
|
}
|
|
70423
70575
|
function lastCompleted(ctx, projectId) {
|
|
70424
70576
|
const at = sql41`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
|
|
70425
|
-
return ctx.db.select({ id: runs.id, at }).from(runs).where(
|
|
70577
|
+
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
70578
|
}
|
|
70427
70579
|
function isIncomplete(run) {
|
|
70428
70580
|
return run.status === RunStatuses.failed || run.status === RunStatuses.partial;
|
|
@@ -70436,7 +70588,7 @@ function unsettledRuns(ctx, projectId, firing, profiles) {
|
|
|
70436
70588
|
const incomplete = firing.filter(isIncomplete);
|
|
70437
70589
|
const firedAt = firing[firing.length - 1]?.createdAt;
|
|
70438
70590
|
if (incomplete.length === 0 || !firedAt) return incomplete;
|
|
70439
|
-
const refreshed = new Set(ctx.db.select({ profileId: seoJobs.profileId }).from(runs).innerJoin(seoJobs,
|
|
70591
|
+
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
70592
|
return incomplete.filter((run) => {
|
|
70441
70593
|
const profile = profiles.get(run.id) ?? null;
|
|
70442
70594
|
return profile === null ? refreshed.size === 0 : !refreshed.has(profile);
|
|
@@ -70446,7 +70598,7 @@ var COUNT_LIST = new Intl.ListFormat("en", { style: "long", type: "conjunction"
|
|
|
70446
70598
|
function refreshReason(ctx, run) {
|
|
70447
70599
|
const stored = parseRunError(run.error);
|
|
70448
70600
|
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,
|
|
70601
|
+
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
70602
|
const failed = tasks.filter((task) => task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"]);
|
|
70451
70603
|
const partial = tasks.filter((task) => task.status === SeoRunStatuses.partial).length;
|
|
70452
70604
|
const cancelled = tasks.filter((task) => task.status === SeoRunStatuses.cancelled).length;
|
|
@@ -70466,7 +70618,7 @@ function refreshReason(ctx, run) {
|
|
|
70466
70618
|
return `Of ${tasks.length} ${unit}, ${COUNT_LIST.format(counts2)}.${errors ? ` ${errors}` : ""}`;
|
|
70467
70619
|
}
|
|
70468
70620
|
function gradeSeoRefresh(ctx, project) {
|
|
70469
|
-
const schedule = ctx.db.select().from(schedules).where(
|
|
70621
|
+
const schedule = ctx.db.select().from(schedules).where(and86(eq100(schedules.projectId, project.id), eq100(schedules.kind, SchedulableRunKinds["seo-sync"]))).get();
|
|
70470
70622
|
if (!schedule) return skipped2("seo.data.not-scheduled", "No SEO refresh schedule is set for this project.");
|
|
70471
70623
|
if (!schedule.enabled) return skipped2("seo.data.not-scheduled", "The SEO refresh schedule is disabled.");
|
|
70472
70624
|
const name = project.name;
|
|
@@ -70538,7 +70690,7 @@ function gradeSeoRefresh(ctx, project) {
|
|
|
70538
70690
|
};
|
|
70539
70691
|
}
|
|
70540
70692
|
if (now >= Date.parse(deadlineAt)) {
|
|
70541
|
-
const active = ctx.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
70693
|
+
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
70694
|
const since = last ? `No SEO refresh has completed since ${day(last.at)}.` : "No SEO refresh has completed since the schedule was enabled.";
|
|
70543
70695
|
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
70696
|
return {
|
|
@@ -70848,7 +71000,7 @@ var siteReachabilityCheck = {
|
|
|
70848
71000
|
var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
|
|
70849
71001
|
|
|
70850
71002
|
// ../api-routes/src/doctor/checks/api-hosts.ts
|
|
70851
|
-
import { eq as
|
|
71003
|
+
import { eq as eq102 } from "drizzle-orm";
|
|
70852
71004
|
|
|
70853
71005
|
// ../api-routes/src/api-host-probe.ts
|
|
70854
71006
|
import dns4 from "dns/promises";
|
|
@@ -70954,7 +71106,7 @@ async function probeApiHost(host, options = {}) {
|
|
|
70954
71106
|
}
|
|
70955
71107
|
|
|
70956
71108
|
// ../api-routes/src/doctor/checks/integration-sync.ts
|
|
70957
|
-
import { and as
|
|
71109
|
+
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
71110
|
var SYNC_FAILURE_WINDOW = 3;
|
|
70959
71111
|
var SUMMARY_ERROR_LIMIT = 300;
|
|
70960
71112
|
var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
|
|
@@ -70981,7 +71133,7 @@ function gscConnection(ctx, project) {
|
|
|
70981
71133
|
function gbpConnection(ctx, project) {
|
|
70982
71134
|
if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
|
|
70983
71135
|
if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
|
|
70984
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(
|
|
71136
|
+
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
71137
|
return selected ? CONNECTED : { state: "no-selected-locations" };
|
|
70986
71138
|
}
|
|
70987
71139
|
function gaConnection(ctx, project) {
|
|
@@ -71054,16 +71206,16 @@ function latestErrorText(raw) {
|
|
|
71054
71206
|
return parsed ? formatRunErrorOneLine(parsed) : "no error was recorded";
|
|
71055
71207
|
}
|
|
71056
71208
|
function readSyncRunState(db, projectId, kind, options = {}) {
|
|
71057
|
-
const scope =
|
|
71058
|
-
|
|
71059
|
-
|
|
71060
|
-
...options.sourceId ? [
|
|
71209
|
+
const scope = and87(
|
|
71210
|
+
eq101(runs.projectId, projectId),
|
|
71211
|
+
eq101(runs.kind, kind),
|
|
71212
|
+
...options.sourceId ? [eq101(runs.sourceId, options.sourceId)] : [],
|
|
71061
71213
|
...options.nothingToSyncErrors?.length ? [or19(isNull14(runs.error), notInArray3(runs.error, [...options.nothingToSyncErrors]))] : []
|
|
71062
71214
|
);
|
|
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(
|
|
71215
|
+
const graded = options.since ? and87(scope, gte28(runs.createdAt, options.since)) : scope;
|
|
71216
|
+
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();
|
|
71217
|
+
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();
|
|
71218
|
+
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
71219
|
const failuresSinceSuccess = Number(failureCount?.count ?? 0);
|
|
71068
71220
|
const latest = recent[0];
|
|
71069
71221
|
return {
|
|
@@ -71160,7 +71312,7 @@ function connectionFor(prefix, ctx, project) {
|
|
|
71160
71312
|
if (prefix === "google-ads") return googleMarketingConnection("googleAds")(ctx);
|
|
71161
71313
|
if (prefix === "gtm") return googleMarketingConnection("gtm")(ctx);
|
|
71162
71314
|
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(
|
|
71315
|
+
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
71316
|
}
|
|
71165
71317
|
function syncCommand(prefix) {
|
|
71166
71318
|
if (prefix === "gsc") return "google sync";
|
|
@@ -71205,7 +71357,7 @@ function definition(prefix) {
|
|
|
71205
71357
|
var API_HOST_CHECKS = Object.keys(API_HOST_TABLE).map(definition);
|
|
71206
71358
|
|
|
71207
71359
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
71208
|
-
import { and as
|
|
71360
|
+
import { and as and88, eq as eq103 } from "drizzle-orm";
|
|
71209
71361
|
var RECENT_SYNC_WARN_DAYS = 4;
|
|
71210
71362
|
var RECENT_SYNC_FAIL_DAYS = 30;
|
|
71211
71363
|
function skippedNoProject3() {
|
|
@@ -71225,7 +71377,7 @@ function storeUnavailable() {
|
|
|
71225
71377
|
};
|
|
71226
71378
|
}
|
|
71227
71379
|
function hasSelectedLocation(ctx, projectId) {
|
|
71228
|
-
return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(
|
|
71380
|
+
return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and88(eq103(gbpLocations.projectId, projectId), eq103(gbpLocations.selected, true))).limit(1).get();
|
|
71229
71381
|
}
|
|
71230
71382
|
async function resolveGbpToken(ctx) {
|
|
71231
71383
|
if (!ctx.project) return { ok: false, output: skippedNoProject3() };
|
|
@@ -71485,7 +71637,7 @@ var recentSyncCheck = {
|
|
|
71485
71637
|
title: "GBP recent sync",
|
|
71486
71638
|
run: (ctx) => {
|
|
71487
71639
|
if (!ctx.project) return skippedNoProject3();
|
|
71488
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
71640
|
+
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
71641
|
if (selected.length === 0) {
|
|
71490
71642
|
return {
|
|
71491
71643
|
status: CheckStatuses.skipped,
|
|
@@ -71545,7 +71697,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
71545
71697
|
);
|
|
71546
71698
|
|
|
71547
71699
|
// ../api-routes/src/doctor/checks/places.ts
|
|
71548
|
-
import { eq as
|
|
71700
|
+
import { eq as eq104 } from "drizzle-orm";
|
|
71549
71701
|
var apiKeyCheck = {
|
|
71550
71702
|
id: "gbp.places.api-key",
|
|
71551
71703
|
category: CheckCategories.auth,
|
|
@@ -71590,7 +71742,7 @@ var apiKeyCheck = {
|
|
|
71590
71742
|
details: { tier: cfg.tier }
|
|
71591
71743
|
};
|
|
71592
71744
|
}
|
|
71593
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
71745
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq104(gbpLocations.projectId, ctx.project.id)).all();
|
|
71594
71746
|
const selected = rows.filter((r) => r.selected);
|
|
71595
71747
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
71596
71748
|
const details = {
|
|
@@ -71623,7 +71775,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
|
|
|
71623
71775
|
);
|
|
71624
71776
|
|
|
71625
71777
|
// ../api-routes/src/doctor/checks/gbp-reviews.ts
|
|
71626
|
-
import { and as
|
|
71778
|
+
import { and as and89, desc as desc46, eq as eq105, gte as gte29, inArray as inArray47, sql as sql42 } from "drizzle-orm";
|
|
71627
71779
|
var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
|
|
71628
71780
|
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
71781
|
var reviewsAccessCheck = {
|
|
@@ -71651,7 +71803,7 @@ var reviewsAccessCheck = {
|
|
|
71651
71803
|
reviewsAccess: gbpLocations.reviewsAccess,
|
|
71652
71804
|
reviewsAccessReason: gbpLocations.reviewsAccessReason,
|
|
71653
71805
|
reviewsCheckedAt: gbpLocations.reviewsCheckedAt
|
|
71654
|
-
}).from(gbpLocations).where(
|
|
71806
|
+
}).from(gbpLocations).where(and89(eq105(gbpLocations.projectId, project.id), eq105(gbpLocations.selected, true))).all();
|
|
71655
71807
|
if (locations.length === 0) {
|
|
71656
71808
|
return {
|
|
71657
71809
|
status: CheckStatuses.skipped,
|
|
@@ -71671,7 +71823,7 @@ var reviewsAccessCheck = {
|
|
|
71671
71823
|
const places = ctx.getPlacesConfig?.();
|
|
71672
71824
|
const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
|
|
71673
71825
|
const v4Worked = new Set(
|
|
71674
|
-
ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(
|
|
71826
|
+
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
71827
|
);
|
|
71676
71828
|
const coverageOf = (loc) => {
|
|
71677
71829
|
if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
|
|
@@ -71705,7 +71857,7 @@ var reviewsAccessCheck = {
|
|
|
71705
71857
|
none > 0 ? `${none} have no review source` : null
|
|
71706
71858
|
].filter((part) => part !== null);
|
|
71707
71859
|
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(
|
|
71860
|
+
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
71861
|
const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
|
|
71710
71862
|
if (none > 0 && subscribed) {
|
|
71711
71863
|
return {
|
|
@@ -71744,8 +71896,8 @@ var reviewDeliveryCheck = {
|
|
|
71744
71896
|
attempts: reviewWebhookDeliveries.attempts,
|
|
71745
71897
|
lastTouched,
|
|
71746
71898
|
payload: reviewWebhookDeliveries.payload
|
|
71747
|
-
}).from(reviewWebhookDeliveries).where(
|
|
71748
|
-
|
|
71899
|
+
}).from(reviewWebhookDeliveries).where(and89(
|
|
71900
|
+
eq105(reviewWebhookDeliveries.projectId, ctx.project.id),
|
|
71749
71901
|
inArray47(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
|
|
71750
71902
|
gte29(lastTouched, since)
|
|
71751
71903
|
)).orderBy(desc46(lastTouched)).all();
|
|
@@ -71757,7 +71909,7 @@ var reviewDeliveryCheck = {
|
|
|
71757
71909
|
remediation: null
|
|
71758
71910
|
};
|
|
71759
71911
|
}
|
|
71760
|
-
const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(
|
|
71912
|
+
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
71913
|
const deliveries = rows.map((row) => {
|
|
71762
71914
|
const review = row.payload.reviews[0];
|
|
71763
71915
|
return {
|
|
@@ -72331,7 +72483,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
72331
72483
|
];
|
|
72332
72484
|
|
|
72333
72485
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
72334
|
-
import { and as
|
|
72486
|
+
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
72487
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
72336
72488
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
72337
72489
|
function isCloudflareDirectPush(source) {
|
|
@@ -72370,8 +72522,8 @@ function skippedNoProject5() {
|
|
|
72370
72522
|
function loadProbes(ctx) {
|
|
72371
72523
|
if (!ctx.project) return [];
|
|
72372
72524
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
72373
|
-
|
|
72374
|
-
|
|
72525
|
+
and90(
|
|
72526
|
+
eq106(trafficSources.projectId, ctx.project.id),
|
|
72375
72527
|
ne17(trafficSources.status, TrafficSourceStatuses.archived)
|
|
72376
72528
|
)
|
|
72377
72529
|
).all();
|
|
@@ -72509,8 +72661,8 @@ var recentDataCheck = {
|
|
|
72509
72661
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
72510
72662
|
const recentCrawlers = Number(
|
|
72511
72663
|
ctx.db.select({ total: sql43`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
72512
|
-
|
|
72513
|
-
|
|
72664
|
+
and90(
|
|
72665
|
+
eq106(crawlerEventsHourly.projectId, ctx.project.id),
|
|
72514
72666
|
inArray48(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
72515
72667
|
gte30(crawlerEventsHourly.tsHour, warnCutoff)
|
|
72516
72668
|
)
|
|
@@ -72518,8 +72670,8 @@ var recentDataCheck = {
|
|
|
72518
72670
|
);
|
|
72519
72671
|
const recentReferrals = Number(
|
|
72520
72672
|
ctx.db.select({ total: sql43`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
72521
|
-
|
|
72522
|
-
|
|
72673
|
+
and90(
|
|
72674
|
+
eq106(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
72523
72675
|
inArray48(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
72524
72676
|
gte30(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
72525
72677
|
)
|
|
@@ -72527,8 +72679,8 @@ var recentDataCheck = {
|
|
|
72527
72679
|
);
|
|
72528
72680
|
const recentUserFetches = Number(
|
|
72529
72681
|
ctx.db.select({ total: sql43`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
72530
|
-
|
|
72531
|
-
|
|
72682
|
+
and90(
|
|
72683
|
+
eq106(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
72532
72684
|
inArray48(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
72533
72685
|
gte30(aiUserFetchEventsHourly.tsHour, warnCutoff)
|
|
72534
72686
|
)
|
|
@@ -72553,8 +72705,8 @@ var recentDataCheck = {
|
|
|
72553
72705
|
}
|
|
72554
72706
|
const olderCrawlers = Number(
|
|
72555
72707
|
ctx.db.select({ total: sql43`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
72556
|
-
|
|
72557
|
-
|
|
72708
|
+
and90(
|
|
72709
|
+
eq106(crawlerEventsHourly.projectId, ctx.project.id),
|
|
72558
72710
|
inArray48(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
72559
72711
|
gte30(crawlerEventsHourly.tsHour, failCutoff)
|
|
72560
72712
|
)
|
|
@@ -72562,8 +72714,8 @@ var recentDataCheck = {
|
|
|
72562
72714
|
);
|
|
72563
72715
|
const olderReferrals = Number(
|
|
72564
72716
|
ctx.db.select({ total: sql43`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
72565
|
-
|
|
72566
|
-
|
|
72717
|
+
and90(
|
|
72718
|
+
eq106(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
72567
72719
|
inArray48(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
72568
72720
|
gte30(aiReferralEventsHourly.tsHour, failCutoff)
|
|
72569
72721
|
)
|
|
@@ -72571,8 +72723,8 @@ var recentDataCheck = {
|
|
|
72571
72723
|
);
|
|
72572
72724
|
const olderUserFetches = Number(
|
|
72573
72725
|
ctx.db.select({ total: sql43`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
72574
|
-
|
|
72575
|
-
|
|
72726
|
+
and90(
|
|
72727
|
+
eq106(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
72576
72728
|
inArray48(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
72577
72729
|
gte30(aiUserFetchEventsHourly.tsHour, failCutoff)
|
|
72578
72730
|
)
|
|
@@ -72899,10 +73051,10 @@ var syncLagCheck = {
|
|
|
72899
73051
|
}
|
|
72900
73052
|
};
|
|
72901
73053
|
function lastQueueStopReason(ctx, sourceId) {
|
|
72902
|
-
const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(
|
|
72903
|
-
|
|
72904
|
-
|
|
72905
|
-
|
|
73054
|
+
const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and90(
|
|
73055
|
+
eq106(runs.projectId, ctx.project.id),
|
|
73056
|
+
eq106(runs.kind, RunKinds["traffic-sync"]),
|
|
73057
|
+
eq106(runs.sourceId, sourceId),
|
|
72906
73058
|
isNotNull9(runs.syncSummary)
|
|
72907
73059
|
)).orderBy(desc47(runs.createdAt)).limit(1).get();
|
|
72908
73060
|
return row?.syncSummary?.type === "cloudflare-queue" ? row.syncSummary.stopReason : null;
|
|
@@ -73702,7 +73854,7 @@ async function doctorRoutes(app, opts) {
|
|
|
73702
73854
|
}
|
|
73703
73855
|
|
|
73704
73856
|
// ../api-routes/src/status.ts
|
|
73705
|
-
import { and as
|
|
73857
|
+
import { and as and91, desc as desc48, eq as eq107, isNull as isNull15 } from "drizzle-orm";
|
|
73706
73858
|
function ageDays(iso2) {
|
|
73707
73859
|
if (!iso2) return null;
|
|
73708
73860
|
const ms = Date.now() - Date.parse(iso2);
|
|
@@ -73752,7 +73904,7 @@ function makeRow(integration, label, runKind, state, staleAfterDays, dataThrough
|
|
|
73752
73904
|
};
|
|
73753
73905
|
}
|
|
73754
73906
|
function latestDate2(db, table, projectId) {
|
|
73755
|
-
const row = db.select({ date: table.date }).from(table).where(
|
|
73907
|
+
const row = db.select({ date: table.date }).from(table).where(eq107(table.projectId, projectId)).orderBy(desc48(table.date)).limit(1).get();
|
|
73756
73908
|
return row?.date ?? null;
|
|
73757
73909
|
}
|
|
73758
73910
|
function projectRows(opts, project) {
|
|
@@ -73767,7 +73919,7 @@ function projectRows(opts, project) {
|
|
|
73767
73919
|
const rows = [];
|
|
73768
73920
|
const gsc = gscConnection(ctx, info);
|
|
73769
73921
|
if (connected(gsc)) {
|
|
73770
|
-
const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(
|
|
73922
|
+
const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq107(gscDataWatermarks.projectId, project.id)).limit(1).get();
|
|
73771
73923
|
const state = readSyncRunState(opts.db, project.id, RunKinds["gsc-sync"], { since: gsc.since });
|
|
73772
73924
|
const dataThrough = watermarkRow?.dataThroughDate ?? latestDate2(opts.db, gscDailyTotals, project.id);
|
|
73773
73925
|
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 +73935,12 @@ function projectRows(opts, project) {
|
|
|
73783
73935
|
rows.push(makeRow("bing", "Bing Webmaster Tools", RunKinds["bing-inspect-sitemap"], readSyncRunState(opts.db, project.id, RunKinds["bing-inspect-sitemap"]), null));
|
|
73784
73936
|
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
73937
|
}
|
|
73786
|
-
const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(
|
|
73938
|
+
const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq107(adsConnections.projectId, project.id)).limit(1).get();
|
|
73787
73939
|
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
73940
|
const marketing = opts.getGoogleMarketingDoctorInput?.(ctx);
|
|
73789
73941
|
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
73942
|
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(
|
|
73943
|
+
const sources = opts.db.select().from(trafficSources).where(and91(eq107(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
|
|
73792
73944
|
for (const source of sources) {
|
|
73793
73945
|
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
73946
|
const lag = source.lastSyncedAt ? Date.now() - Date.parse(source.lastSyncedAt) : Number.POSITIVE_INFINITY;
|
|
@@ -73800,7 +73952,7 @@ function projectRows(opts, project) {
|
|
|
73800
73952
|
}
|
|
73801
73953
|
function projectStatus(opts, project) {
|
|
73802
73954
|
const integrations = projectRows(opts, project);
|
|
73803
|
-
const healthRow = opts.db.select().from(doctorHealthState).where(
|
|
73955
|
+
const healthRow = opts.db.select().from(doctorHealthState).where(eq107(doctorHealthState.projectId, project.id)).limit(1).get();
|
|
73804
73956
|
const health = healthRow ? { status: healthRow.status, code: healthRow.code, summary: healthRow.summary, checkedAt: healthRow.checkedAt, notifiedAt: healthRow.notifiedAt } : null;
|
|
73805
73957
|
const rank = { ok: 0, "never-synced": 1, warning: 2, stale: 3, failing: 4 };
|
|
73806
73958
|
const healthState = health?.status === "fail" ? "failing" : health?.status === "warn" ? "warning" : "ok";
|
|
@@ -73820,7 +73972,7 @@ async function statusRoutes(app, opts) {
|
|
|
73820
73972
|
if (request.query.problems === "true") result.projects = result.projects.map((item2) => ({ ...item2, integrations: item2.integrations.filter((row) => row.state !== "ok") }));
|
|
73821
73973
|
return instanceStatusDtoSchema.parse(result);
|
|
73822
73974
|
}
|
|
73823
|
-
const projectsRows = (scopedId ? opts.db.select().from(projects).where(
|
|
73975
|
+
const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq107(projects.id, scopedId)) : opts.db.select().from(projects)).all();
|
|
73824
73976
|
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
73977
|
for (const project of output.projects) {
|
|
73826
73978
|
const key = project.state === "never-synced" ? "neverSynced" : project.state;
|
|
@@ -73833,7 +73985,7 @@ async function statusRoutes(app, opts) {
|
|
|
73833
73985
|
|
|
73834
73986
|
// ../api-routes/src/discovery/routes.ts
|
|
73835
73987
|
import crypto57 from "crypto";
|
|
73836
|
-
import { and as
|
|
73988
|
+
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
73989
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
73838
73990
|
async function discoveryRoutes(app, opts) {
|
|
73839
73991
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -73867,13 +74019,13 @@ async function discoveryRoutes(app, opts) {
|
|
|
73867
74019
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
73868
74020
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
73869
74021
|
const decision = app.db.transaction((tx) => {
|
|
73870
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
73871
|
-
|
|
73872
|
-
|
|
74022
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and92(
|
|
74023
|
+
eq108(discoverySessions.projectId, project.id),
|
|
74024
|
+
eq108(discoverySessions.icpDescription, icpDescription),
|
|
73873
74025
|
// Buyer is part of session identity: it changes the seed prompt's
|
|
73874
74026
|
// semantics, so a request with a different (or no) buyer must start
|
|
73875
74027
|
// its own session, never adopt another buyer's probes.
|
|
73876
|
-
parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) :
|
|
74028
|
+
parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq108(discoverySessions.buyerDescription, parsed.data.buyerDescription),
|
|
73877
74029
|
// Locations are identity too: a different service-area subset seeds
|
|
73878
74030
|
// and probes a different geo, so it must never reuse another geo's
|
|
73879
74031
|
// session. resolveLocations is deterministic (project-config order),
|
|
@@ -73883,11 +74035,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
73883
74035
|
// locations a NULL row's subset is unknowable, so it conservatively
|
|
73884
74036
|
// never reuses — a one-time, bounded (2h window) non-reuse after
|
|
73885
74037
|
// upgrade, never a wrong reuse.
|
|
73886
|
-
locations.length === 0 ? or20(isNull16(discoverySessions.locations),
|
|
74038
|
+
locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq108(discoverySessions.locations, locations)) : eq108(discoverySessions.locations, locations),
|
|
73887
74039
|
// Seed provider set is identity: a different phrasing distribution
|
|
73888
74040
|
// must never reuse another set's session. Null (= Gemini-only
|
|
73889
74041
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
73890
|
-
seedProviders == null ? isNull16(discoverySessions.seedProviders) :
|
|
74042
|
+
seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq108(discoverySessions.seedProviders, seedProviders),
|
|
73891
74043
|
inArray49(discoverySessions.status, [
|
|
73892
74044
|
DiscoverySessionStatuses.queued,
|
|
73893
74045
|
DiscoverySessionStatuses.seeding,
|
|
@@ -73963,7 +74115,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
73963
74115
|
const project = resolveProject(app.db, request.params.name);
|
|
73964
74116
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
73965
74117
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
73966
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
74118
|
+
const rows = app.db.select().from(discoverySessions).where(eq108(discoverySessions.projectId, project.id)).orderBy(desc49(discoverySessions.createdAt)).limit(limit).all();
|
|
73967
74119
|
return reply.send(rows.map(serializeSession));
|
|
73968
74120
|
}
|
|
73969
74121
|
);
|
|
@@ -73971,11 +74123,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
73971
74123
|
"/projects/:name/discover/sessions/:id",
|
|
73972
74124
|
async (request, reply) => {
|
|
73973
74125
|
const project = resolveProject(app.db, request.params.name);
|
|
73974
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
74126
|
+
const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
|
|
73975
74127
|
if (!session || session.projectId !== project.id) {
|
|
73976
74128
|
throw notFound("Discovery session", request.params.id);
|
|
73977
74129
|
}
|
|
73978
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
74130
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
|
|
73979
74131
|
const detail = {
|
|
73980
74132
|
...serializeSession(session),
|
|
73981
74133
|
probes: probeRows.map(serializeProbe)
|
|
@@ -73987,7 +74139,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
73987
74139
|
"/projects/:name/discover/sessions/:id/harvest",
|
|
73988
74140
|
async (request, reply) => {
|
|
73989
74141
|
const project = resolveProject(app.db, request.params.name);
|
|
73990
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
74142
|
+
const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
|
|
73991
74143
|
if (!session || session.projectId !== project.id) {
|
|
73992
74144
|
throw notFound("Discovery session", request.params.id);
|
|
73993
74145
|
}
|
|
@@ -73995,7 +74147,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
73995
74147
|
const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
|
|
73996
74148
|
const applyAnchor = request.query.anchor !== "false";
|
|
73997
74149
|
const provider = session.seedProvider ?? "gemini";
|
|
73998
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
74150
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
|
|
73999
74151
|
const extract = opts.harvestSearchQueries;
|
|
74000
74152
|
const probesWithQueries = probeRows.map((row) => {
|
|
74001
74153
|
if (!extract || !row.rawResponse) return { searchQueries: [] };
|
|
@@ -74006,7 +74158,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
74006
74158
|
return { searchQueries: [] };
|
|
74007
74159
|
}
|
|
74008
74160
|
});
|
|
74009
|
-
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(
|
|
74161
|
+
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq108(queries.projectId, project.id)).all().map((r) => r.query);
|
|
74010
74162
|
const anchorTerms = buildHarvestAnchorTerms(
|
|
74011
74163
|
[session.icpDescription ?? "", ...trackedQueries],
|
|
74012
74164
|
effectiveDomains(project)
|
|
@@ -74053,11 +74205,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
74053
74205
|
"/projects/:name/discover/sessions/:id/promote",
|
|
74054
74206
|
async (request, reply) => {
|
|
74055
74207
|
const project = resolveProject(app.db, request.params.name);
|
|
74056
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
74208
|
+
const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
|
|
74057
74209
|
if (!session || session.projectId !== project.id) {
|
|
74058
74210
|
throw notFound("Discovery session", request.params.id);
|
|
74059
74211
|
}
|
|
74060
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
74212
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
|
|
74061
74213
|
const cited = /* @__PURE__ */ new Set();
|
|
74062
74214
|
const aspirational = /* @__PURE__ */ new Set();
|
|
74063
74215
|
const wasted = /* @__PURE__ */ new Set();
|
|
@@ -74092,7 +74244,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
74092
74244
|
);
|
|
74093
74245
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
74094
74246
|
const project = resolveProject(app.db, request.params.name);
|
|
74095
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
74247
|
+
const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
|
|
74096
74248
|
if (!session || session.projectId !== project.id) {
|
|
74097
74249
|
throw notFound("Discovery session", request.params.id);
|
|
74098
74250
|
}
|
|
@@ -74115,7 +74267,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
74115
74267
|
const bucketSet = new Set(buckets);
|
|
74116
74268
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
74117
74269
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
74118
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
74270
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
|
|
74119
74271
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
74120
74272
|
for (const probe of probeRows) {
|
|
74121
74273
|
if (!probe.bucket) continue;
|
|
@@ -74123,7 +74275,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
74123
74275
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
74124
74276
|
}
|
|
74125
74277
|
const existingQueries = new Set(
|
|
74126
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
74278
|
+
app.db.select({ query: queries.query }).from(queries).where(eq108(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
74127
74279
|
);
|
|
74128
74280
|
const promotedQueries = [];
|
|
74129
74281
|
const skippedQueries = [];
|
|
@@ -74337,7 +74489,7 @@ function planCompetitorPromotion(input) {
|
|
|
74337
74489
|
|
|
74338
74490
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
74339
74491
|
import crypto58 from "crypto";
|
|
74340
|
-
import { eq as
|
|
74492
|
+
import { eq as eq109 } from "drizzle-orm";
|
|
74341
74493
|
var DEFAULT_MAX_PROBES = 100;
|
|
74342
74494
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
74343
74495
|
function classifyProbeBucket(input) {
|
|
@@ -74423,7 +74575,7 @@ async function executeDiscovery(opts) {
|
|
|
74423
74575
|
status: DiscoverySessionStatuses.seeding,
|
|
74424
74576
|
dedupThreshold,
|
|
74425
74577
|
startedAt
|
|
74426
|
-
}).where(
|
|
74578
|
+
}).where(eq109(discoverySessions.id, opts.sessionId)).run();
|
|
74427
74579
|
const seedResult = await opts.deps.seed({
|
|
74428
74580
|
project: opts.project,
|
|
74429
74581
|
icpDescription: opts.icpDescription,
|
|
@@ -74484,7 +74636,7 @@ async function executeDiscovery(opts) {
|
|
|
74484
74636
|
dedupBandPairFraction: dedupStats.bandPairFraction,
|
|
74485
74637
|
dedupPairsTotal: dedupStats.pairsTotal,
|
|
74486
74638
|
warning
|
|
74487
|
-
}).where(
|
|
74639
|
+
}).where(eq109(discoverySessions.id, opts.sessionId)).run();
|
|
74488
74640
|
const probeLocation = opts.locations?.[0];
|
|
74489
74641
|
const probeResults = await mapWithConcurrency(
|
|
74490
74642
|
probedCanonicals,
|
|
@@ -74538,7 +74690,7 @@ async function executeDiscovery(opts) {
|
|
|
74538
74690
|
wastedCount: buckets["wasted-surface"],
|
|
74539
74691
|
competitorMap,
|
|
74540
74692
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
74541
|
-
}).where(
|
|
74693
|
+
}).where(eq109(discoverySessions.id, opts.sessionId)).run();
|
|
74542
74694
|
upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
|
|
74543
74695
|
return {
|
|
74544
74696
|
buckets,
|
|
@@ -74578,7 +74730,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
74578
74730
|
status: DiscoverySessionStatuses.failed,
|
|
74579
74731
|
error,
|
|
74580
74732
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
74581
|
-
}).where(
|
|
74733
|
+
}).where(eq109(discoverySessions.id, sessionId)).run();
|
|
74582
74734
|
}
|
|
74583
74735
|
function dedupeStrings(input) {
|
|
74584
74736
|
const seen2 = /* @__PURE__ */ new Set();
|
|
@@ -74596,12 +74748,12 @@ function dedupeStrings(input) {
|
|
|
74596
74748
|
|
|
74597
74749
|
// ../api-routes/src/technical-aeo.ts
|
|
74598
74750
|
import crypto59 from "crypto";
|
|
74599
|
-
import { and as
|
|
74751
|
+
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
74752
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
74601
74753
|
|
|
74602
74754
|
// ../api-routes/src/technical-aeo-issues.ts
|
|
74603
74755
|
import { createHash as createHash10 } from "crypto";
|
|
74604
|
-
import { and as
|
|
74756
|
+
import { and as and93, asc as asc26, eq as eq110, gt as gt10, sql as sql44 } from "drizzle-orm";
|
|
74605
74757
|
var ADVISORY_CODES = /* @__PURE__ */ new Set([
|
|
74606
74758
|
"entity-consistency.title.short",
|
|
74607
74759
|
"site.duplicate-title",
|
|
@@ -74746,10 +74898,10 @@ function readSiteAuditIssues(input) {
|
|
|
74746
74898
|
}
|
|
74747
74899
|
}
|
|
74748
74900
|
if (snapshot.detailsAvailable && snapshot.attemptId) {
|
|
74749
|
-
const scope =
|
|
74750
|
-
|
|
74751
|
-
|
|
74752
|
-
|
|
74901
|
+
const scope = and93(
|
|
74902
|
+
eq110(siteCrawlPages.projectId, project.id),
|
|
74903
|
+
eq110(siteCrawlPages.runId, snapshot.runId),
|
|
74904
|
+
eq110(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
74753
74905
|
);
|
|
74754
74906
|
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
74907
|
if (size + crossBytes > MAX_EVIDENCE_BYTES) {
|
|
@@ -74762,7 +74914,7 @@ function readSiteAuditIssues(input) {
|
|
|
74762
74914
|
url: siteCrawlPages.url,
|
|
74763
74915
|
auditScore: siteCrawlPages.auditScore,
|
|
74764
74916
|
auditFields: siteCrawlPages.auditFields
|
|
74765
|
-
}).from(siteCrawlPages).where(
|
|
74917
|
+
}).from(siteCrawlPages).where(and93(scope, after === void 0 ? void 0 : gt10(siteCrawlPages.nodeKey, after))).orderBy(asc26(siteCrawlPages.nodeKey)).limit(200).all();
|
|
74766
74918
|
for (const page2 of pages) {
|
|
74767
74919
|
evidenceHash.update(JSON.stringify(page2));
|
|
74768
74920
|
const fields = record(page2.auditFields);
|
|
@@ -74899,9 +75051,9 @@ var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
|
|
|
74899
75051
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
74900
75052
|
function surfaceableSiteAuditFilters(projectId) {
|
|
74901
75053
|
return [
|
|
74902
|
-
|
|
74903
|
-
|
|
74904
|
-
|
|
75054
|
+
eq111(siteAuditSnapshots.projectId, projectId),
|
|
75055
|
+
eq111(runs.projectId, projectId),
|
|
75056
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
74905
75057
|
inArray50(runs.status, SURFACEABLE_STATUSES),
|
|
74906
75058
|
notProbeRun()
|
|
74907
75059
|
];
|
|
@@ -75142,8 +75294,8 @@ function parseLinkKind(value) {
|
|
|
75142
75294
|
return allowed.data;
|
|
75143
75295
|
}
|
|
75144
75296
|
function linkKindFilter(linkKind) {
|
|
75145
|
-
if (linkKind === SiteHealthLinkKinds.content) return
|
|
75146
|
-
if (linkKind === SiteHealthLinkKinds.template) return
|
|
75297
|
+
if (linkKind === SiteHealthLinkKinds.content) return eq111(siteCrawlEdges.isTemplate, false);
|
|
75298
|
+
if (linkKind === SiteHealthLinkKinds.template) return eq111(siteCrawlEdges.isTemplate, true);
|
|
75147
75299
|
return void 0;
|
|
75148
75300
|
}
|
|
75149
75301
|
var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
|
|
@@ -75247,9 +75399,9 @@ function changedFields(before, after, fields) {
|
|
|
75247
75399
|
}
|
|
75248
75400
|
async function technicalAeoRoutes(app, opts) {
|
|
75249
75401
|
const visibleCrawlFilters = (projectId) => [
|
|
75250
|
-
|
|
75251
|
-
|
|
75252
|
-
|
|
75402
|
+
eq111(siteCrawlSnapshots.projectId, projectId),
|
|
75403
|
+
eq111(runs.projectId, projectId),
|
|
75404
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
75253
75405
|
inArray50(runs.status, SURFACEABLE_STATUSES),
|
|
75254
75406
|
notProbeRun()
|
|
75255
75407
|
];
|
|
@@ -75257,10 +75409,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75257
75409
|
if (!requestedDate) return void 0;
|
|
75258
75410
|
const date = sql45`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
|
|
75259
75411
|
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,
|
|
75412
|
+
const allDates = and94(...filters);
|
|
75413
|
+
const matchingDates = and94(...filters, eq111(sql45`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
|
|
75414
|
+
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);
|
|
75415
|
+
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
75416
|
return {
|
|
75265
75417
|
recentDates: readDates(allDates),
|
|
75266
75418
|
totalDates: countDates(allDates),
|
|
@@ -75270,15 +75422,15 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75270
75422
|
};
|
|
75271
75423
|
const resolveCrawl = (projectId, runId, requestedDate, scope = {}) => {
|
|
75272
75424
|
const filters = visibleCrawlFilters(projectId);
|
|
75273
|
-
if (scope.recordedBefore) filters.push(
|
|
75425
|
+
if (scope.recordedBefore) filters.push(lt19(siteCrawlSnapshots.createdAt, scope.recordedBefore));
|
|
75274
75426
|
const newestIfComplete = sql45`case when ${siteCrawlSnapshots.complete} then ${siteCrawlSnapshots.createdAt} end`;
|
|
75275
75427
|
if (requestedDate) {
|
|
75276
75428
|
const nextDate2 = /* @__PURE__ */ new Date(`${requestedDate}T00:00:00.000Z`);
|
|
75277
75429
|
nextDate2.setUTCDate(nextDate2.getUTCDate() + 1);
|
|
75278
|
-
filters.push(gte32(siteCrawlSnapshots.createdAt, requestedDate),
|
|
75430
|
+
filters.push(gte32(siteCrawlSnapshots.createdAt, requestedDate), lt19(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
|
|
75279
75431
|
}
|
|
75280
75432
|
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,
|
|
75433
|
+
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
75434
|
desc50(sql45`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
|
|
75283
75435
|
desc50(siteCrawlSnapshots.complete),
|
|
75284
75436
|
desc50(newestIfComplete),
|
|
@@ -75290,18 +75442,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75290
75442
|
const date = selected.snapshot.createdAt.slice(0, 10);
|
|
75291
75443
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
75292
75444
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
75293
|
-
const sameDate =
|
|
75445
|
+
const sameDate = and94(
|
|
75294
75446
|
...filters,
|
|
75295
75447
|
gte32(siteCrawlSnapshots.createdAt, date),
|
|
75296
|
-
|
|
75448
|
+
lt19(siteCrawlSnapshots.createdAt, nextDate.toISOString())
|
|
75297
75449
|
);
|
|
75298
|
-
const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
75450
|
+
const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
75299
75451
|
const candidates = app.db.select({
|
|
75300
75452
|
runId: siteCrawlSnapshots.runId,
|
|
75301
75453
|
createdAt: siteCrawlSnapshots.createdAt,
|
|
75302
75454
|
complete: siteCrawlSnapshots.complete,
|
|
75303
75455
|
pages: siteCrawlSnapshots.pagesFetched
|
|
75304
|
-
}).from(siteCrawlSnapshots).innerJoin(runs,
|
|
75456
|
+
}).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
75305
75457
|
desc50(siteCrawlSnapshots.complete),
|
|
75306
75458
|
desc50(newestIfComplete),
|
|
75307
75459
|
desc50(siteCrawlSnapshots.pagesFetched),
|
|
@@ -75321,11 +75473,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75321
75473
|
const resolveAudit = (projectId, runId, recordedBefore) => {
|
|
75322
75474
|
const filters = [
|
|
75323
75475
|
...surfaceableSiteAuditFilters(projectId),
|
|
75324
|
-
...recordedBefore ? [
|
|
75476
|
+
...recordedBefore ? [lt19(siteAuditSnapshots.createdAt, recordedBefore)] : []
|
|
75325
75477
|
];
|
|
75326
75478
|
const complete = sql45`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
|
|
75327
75479
|
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,
|
|
75480
|
+
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
75481
|
desc50(sql45`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
|
|
75330
75482
|
desc50(complete),
|
|
75331
75483
|
desc50(newestIfComplete),
|
|
@@ -75347,18 +75499,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75347
75499
|
const date = selected.snap.createdAt.slice(0, 10);
|
|
75348
75500
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
75349
75501
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
75350
|
-
const sameDate =
|
|
75502
|
+
const sameDate = and94(
|
|
75351
75503
|
...filters,
|
|
75352
75504
|
gte32(siteAuditSnapshots.createdAt, date),
|
|
75353
|
-
|
|
75505
|
+
lt19(siteAuditSnapshots.createdAt, nextDate.toISOString())
|
|
75354
75506
|
);
|
|
75355
|
-
const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs,
|
|
75507
|
+
const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
75356
75508
|
const candidates = app.db.select({
|
|
75357
75509
|
runId: siteAuditSnapshots.runId,
|
|
75358
75510
|
createdAt: siteAuditSnapshots.createdAt,
|
|
75359
75511
|
complete,
|
|
75360
75512
|
pages: siteAuditSnapshots.pagesAudited
|
|
75361
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
75513
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
75362
75514
|
desc50(complete),
|
|
75363
75515
|
desc50(newestIfComplete),
|
|
75364
75516
|
desc50(siteAuditSnapshots.pagesAudited),
|
|
@@ -75425,19 +75577,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75425
75577
|
};
|
|
75426
75578
|
const inventorySummaryFor = (scope) => {
|
|
75427
75579
|
if (!scope) return null;
|
|
75428
|
-
const inScope =
|
|
75429
|
-
|
|
75430
|
-
|
|
75431
|
-
|
|
75580
|
+
const inScope = and94(
|
|
75581
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
75582
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
75583
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId)
|
|
75432
75584
|
);
|
|
75433
75585
|
const groups = crawlReasonGroups(inScope);
|
|
75434
75586
|
const total = groups.reduce((sum, group) => sum + group.pages, 0);
|
|
75435
75587
|
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
|
-
|
|
75588
|
+
const urlVersions = app.db.select({ value: count8() }).from(siteCrawlPages).where(and94(inScope, isNotNull10(siteCrawlPages.urlVersionOf))).get()?.value ?? 0;
|
|
75589
|
+
const urlVersionsNotFetched = app.db.select({ value: siteCrawlAttempts.urlVersionsSkipped }).from(siteCrawlAttempts).where(and94(
|
|
75590
|
+
eq111(siteCrawlAttempts.projectId, scope.projectId),
|
|
75591
|
+
eq111(siteCrawlAttempts.runId, scope.runId),
|
|
75592
|
+
eq111(siteCrawlAttempts.id, scope.attemptId)
|
|
75441
75593
|
)).get()?.value ?? 0;
|
|
75442
75594
|
return {
|
|
75443
75595
|
scope: "selected-snapshot",
|
|
@@ -75457,33 +75609,33 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75457
75609
|
const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
|
|
75458
75610
|
const pageInScope = (scope, selector) => {
|
|
75459
75611
|
if (!selector.nodeKey && !selector.url) return null;
|
|
75460
|
-
return app.db.select().from(siteCrawlPages).where(
|
|
75461
|
-
|
|
75462
|
-
|
|
75463
|
-
|
|
75464
|
-
selector.nodeKey ?
|
|
75612
|
+
return app.db.select().from(siteCrawlPages).where(and94(
|
|
75613
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
75614
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
75615
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
75616
|
+
selector.nodeKey ? eq111(siteCrawlPages.nodeKey, selector.nodeKey) : eq111(siteCrawlPages.url, selector.url)
|
|
75465
75617
|
)).orderBy(asc27(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
75466
75618
|
};
|
|
75467
|
-
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(
|
|
75468
|
-
|
|
75469
|
-
|
|
75470
|
-
|
|
75471
|
-
|
|
75619
|
+
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and94(
|
|
75620
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
75621
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
75622
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
75623
|
+
eq111(siteCrawlPages.depth, 0)
|
|
75472
75624
|
)).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
|
-
|
|
75625
|
+
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and94(
|
|
75626
|
+
eq111(runs.id, runId),
|
|
75627
|
+
eq111(runs.projectId, projectId),
|
|
75628
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
75477
75629
|
inArray50(runs.status, SURFACEABLE_STATUSES),
|
|
75478
75630
|
notProbeRun()
|
|
75479
75631
|
)).limit(1).get());
|
|
75480
75632
|
const assertKnownAuditRun = (projectId, runId) => {
|
|
75481
75633
|
if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
|
|
75482
75634
|
};
|
|
75483
|
-
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs,
|
|
75484
|
-
|
|
75485
|
-
|
|
75486
|
-
|
|
75635
|
+
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(
|
|
75636
|
+
eq111(siteAuditSnapshots.projectId, projectId),
|
|
75637
|
+
eq111(runs.projectId, projectId),
|
|
75638
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
75487
75639
|
inArray50(runs.status, SURFACEABLE_STATUSES),
|
|
75488
75640
|
notProbeRun()
|
|
75489
75641
|
)).limit(1).get());
|
|
@@ -75585,9 +75737,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75585
75737
|
};
|
|
75586
75738
|
}
|
|
75587
75739
|
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 =
|
|
75740
|
+
const conds = [eq111(siteAuditPages.projectId, project.id), eq111(siteAuditPages.runId, latest.snap.runId)];
|
|
75741
|
+
if (statusFilter) conds.push(eq111(siteAuditPages.status, statusFilter));
|
|
75742
|
+
const where = and94(...conds);
|
|
75591
75743
|
const totalRow = app.db.select({ value: count8() }).from(siteAuditPages).where(where).get();
|
|
75592
75744
|
const total = totalRow?.value ?? 0;
|
|
75593
75745
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
@@ -75611,7 +75763,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75611
75763
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
75612
75764
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
75613
75765
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
75614
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
75766
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(...surfaceableSiteAuditFilters(project.id))).orderBy(desc50(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
75615
75767
|
return { project: project.name, points: rows.reverse() };
|
|
75616
75768
|
});
|
|
75617
75769
|
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
@@ -75711,10 +75863,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75711
75863
|
{ projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
|
|
75712
75864
|
snapshot.rootUrl
|
|
75713
75865
|
)?.nodeKey ?? null;
|
|
75714
|
-
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(
|
|
75715
|
-
|
|
75716
|
-
|
|
75717
|
-
|
|
75866
|
+
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and94(
|
|
75867
|
+
eq111(siteCrawlGraphLayouts.projectId, project.id),
|
|
75868
|
+
eq111(siteCrawlGraphLayouts.runId, snapshot.runId),
|
|
75869
|
+
eq111(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
|
|
75718
75870
|
)).limit(1).get();
|
|
75719
75871
|
if (!persistedLayout) {
|
|
75720
75872
|
return {
|
|
@@ -75767,9 +75919,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75767
75919
|
const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
|
|
75768
75920
|
const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
|
|
75769
75921
|
const graphScope = [
|
|
75770
|
-
|
|
75771
|
-
|
|
75772
|
-
|
|
75922
|
+
eq111(siteCrawlGraphNodes.projectId, project.id),
|
|
75923
|
+
eq111(siteCrawlGraphNodes.runId, snapshot.runId),
|
|
75924
|
+
eq111(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
|
|
75773
75925
|
];
|
|
75774
75926
|
const nodeRows = app.db.select({
|
|
75775
75927
|
nodeKey: siteCrawlPages.nodeKey,
|
|
@@ -75788,18 +75940,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75788
75940
|
linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
|
|
75789
75941
|
x: siteCrawlGraphNodes.x,
|
|
75790
75942
|
y: siteCrawlGraphNodes.y
|
|
75791
|
-
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages,
|
|
75792
|
-
|
|
75793
|
-
|
|
75794
|
-
|
|
75795
|
-
|
|
75796
|
-
)).where(
|
|
75943
|
+
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and94(
|
|
75944
|
+
eq111(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
|
|
75945
|
+
eq111(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
|
|
75946
|
+
eq111(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
|
|
75947
|
+
eq111(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
|
|
75948
|
+
)).where(and94(...graphScope, lt19(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc27(siteCrawlGraphNodes.sampleRank)).all();
|
|
75797
75949
|
const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
|
|
75798
75950
|
...row,
|
|
75799
75951
|
healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
|
|
75800
75952
|
healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
|
|
75801
75953
|
}));
|
|
75802
|
-
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 :
|
|
75954
|
+
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq111(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
|
|
75803
75955
|
const edges = app.db.select({
|
|
75804
75956
|
edgeKey: siteCrawlGraphEdges.edgeKey,
|
|
75805
75957
|
sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
|
|
@@ -75807,23 +75959,23 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75807
75959
|
followable: siteCrawlGraphEdges.followable,
|
|
75808
75960
|
occurrences: siteCrawlGraphEdges.occurrences,
|
|
75809
75961
|
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
|
-
|
|
75962
|
+
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and94(
|
|
75963
|
+
eq111(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
|
|
75964
|
+
eq111(graphSourceNode.runId, siteCrawlGraphEdges.runId),
|
|
75965
|
+
eq111(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
75966
|
+
eq111(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
|
|
75967
|
+
lt19(graphSourceNode.sampleRank, maxNodes)
|
|
75968
|
+
)).innerJoin(graphTargetNode, and94(
|
|
75969
|
+
eq111(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
|
|
75970
|
+
eq111(graphTargetNode.runId, siteCrawlGraphEdges.runId),
|
|
75971
|
+
eq111(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
75972
|
+
eq111(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
|
|
75973
|
+
lt19(graphTargetNode.sampleRank, maxNodes)
|
|
75974
|
+
)).where(and94(
|
|
75975
|
+
eq111(siteCrawlGraphEdges.projectId, project.id),
|
|
75976
|
+
eq111(siteCrawlGraphEdges.runId, snapshot.runId),
|
|
75977
|
+
eq111(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
|
|
75978
|
+
lt19(siteCrawlGraphEdges.sampleRank, maxEdges),
|
|
75827
75979
|
graphLinkKindFilter
|
|
75828
75980
|
)).orderBy(asc27(siteCrawlGraphEdges.sampleRank)).all();
|
|
75829
75981
|
const totalNodes = persistedLayout.totalNodes;
|
|
@@ -75917,11 +76069,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75917
76069
|
let frontier = [focus.nodeKey];
|
|
75918
76070
|
let queryTruncated = false;
|
|
75919
76071
|
for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
|
|
75920
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
75921
|
-
|
|
75922
|
-
|
|
75923
|
-
|
|
75924
|
-
|
|
76072
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and94(
|
|
76073
|
+
eq111(siteCrawlEdges.projectId, scope.projectId),
|
|
76074
|
+
eq111(siteCrawlEdges.runId, scope.runId),
|
|
76075
|
+
eq111(siteCrawlEdges.attemptId, scope.attemptId),
|
|
76076
|
+
eq111(siteCrawlEdges.internal, true),
|
|
75925
76077
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
75926
76078
|
or21(
|
|
75927
76079
|
inArray50(siteCrawlEdges.sourceNodeKey, frontier),
|
|
@@ -75958,19 +76110,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75958
76110
|
frontier = [...next].sort();
|
|
75959
76111
|
}
|
|
75960
76112
|
const nodeKeys = [...distances.keys()];
|
|
75961
|
-
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
75962
|
-
|
|
75963
|
-
|
|
75964
|
-
|
|
76113
|
+
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and94(
|
|
76114
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
76115
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
76116
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
75965
76117
|
inArray50(siteCrawlPages.nodeKey, nodeKeys)
|
|
75966
76118
|
)).all();
|
|
75967
76119
|
const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
|
|
75968
76120
|
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
|
-
|
|
76121
|
+
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and94(
|
|
76122
|
+
eq111(siteCrawlEdges.projectId, scope.projectId),
|
|
76123
|
+
eq111(siteCrawlEdges.runId, scope.runId),
|
|
76124
|
+
eq111(siteCrawlEdges.attemptId, scope.attemptId),
|
|
76125
|
+
eq111(siteCrawlEdges.internal, true),
|
|
75974
76126
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
75975
76127
|
inArray50(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
|
|
75976
76128
|
inArray50(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
|
|
@@ -76066,22 +76218,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76066
76218
|
let found = fromPage.nodeKey === toPage.nodeKey;
|
|
76067
76219
|
let truncated = false;
|
|
76068
76220
|
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
|
-
|
|
76221
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and94(
|
|
76222
|
+
eq111(siteCrawlEdges.projectId, scope.projectId),
|
|
76223
|
+
eq111(siteCrawlEdges.runId, scope.runId),
|
|
76224
|
+
eq111(siteCrawlEdges.attemptId, scope.attemptId),
|
|
76225
|
+
eq111(siteCrawlEdges.internal, true),
|
|
76226
|
+
eq111(siteCrawlEdges.followable, true),
|
|
76227
|
+
eq111(siteCrawlEdges.relation, "anchor"),
|
|
76076
76228
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
76077
76229
|
inArray50(siteCrawlEdges.sourceNodeKey, frontier)
|
|
76078
76230
|
)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
|
|
76079
76231
|
if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
|
|
76080
76232
|
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
|
-
|
|
76233
|
+
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and94(
|
|
76234
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
76235
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
76236
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
76085
76237
|
inArray50(siteCrawlPages.nodeKey, candidateTargetKeys)
|
|
76086
76238
|
)).all().map((row) => row.nodeKey));
|
|
76087
76239
|
const next = /* @__PURE__ */ new Set();
|
|
@@ -76123,10 +76275,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76123
76275
|
pathEdges.unshift(step.edge);
|
|
76124
76276
|
pathKeys.unshift(step.nodeKey);
|
|
76125
76277
|
}
|
|
76126
|
-
const pathRows = app.db.select().from(siteCrawlPages).where(
|
|
76127
|
-
|
|
76128
|
-
|
|
76129
|
-
|
|
76278
|
+
const pathRows = app.db.select().from(siteCrawlPages).where(and94(
|
|
76279
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
76280
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
76281
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
76130
76282
|
inArray50(siteCrawlPages.nodeKey, pathKeys)
|
|
76131
76283
|
)).all();
|
|
76132
76284
|
const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
|
|
@@ -76155,22 +76307,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76155
76307
|
throw validationError("change must be all, added, removed, or changed");
|
|
76156
76308
|
}
|
|
76157
76309
|
const completeSnapshotFilters = [
|
|
76158
|
-
|
|
76159
|
-
|
|
76160
|
-
|
|
76161
|
-
|
|
76162
|
-
|
|
76310
|
+
eq111(siteCrawlSnapshots.projectId, project.id),
|
|
76311
|
+
eq111(runs.projectId, project.id),
|
|
76312
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
76313
|
+
eq111(runs.status, RunStatuses.completed),
|
|
76314
|
+
eq111(siteCrawlSnapshots.complete, true),
|
|
76163
76315
|
notProbeRun()
|
|
76164
76316
|
];
|
|
76165
|
-
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
76317
|
+
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
76318
|
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,
|
|
76319
|
+
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
76320
|
...completeSnapshotFilters,
|
|
76169
76321
|
or21(
|
|
76170
|
-
|
|
76171
|
-
|
|
76172
|
-
|
|
76173
|
-
|
|
76322
|
+
lt19(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
76323
|
+
and94(
|
|
76324
|
+
eq111(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
76325
|
+
lt19(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
|
|
76174
76326
|
)
|
|
76175
76327
|
)
|
|
76176
76328
|
)).orderBy(desc50(siteCrawlSnapshots.createdAt), desc50(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
|
|
@@ -76414,17 +76566,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76414
76566
|
const visibleKeys = keyRows.slice(0, limit);
|
|
76415
76567
|
const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
|
|
76416
76568
|
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
|
-
|
|
76569
|
+
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and94(
|
|
76570
|
+
eq111(siteCrawlPages.projectId, scope.projectId),
|
|
76571
|
+
eq111(siteCrawlPages.runId, scope.runId),
|
|
76572
|
+
eq111(siteCrawlPages.attemptId, scope.attemptId),
|
|
76421
76573
|
inArray50(siteCrawlPages.nodeKey, pageKeys)
|
|
76422
76574
|
)).all();
|
|
76423
|
-
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(
|
|
76424
|
-
|
|
76425
|
-
|
|
76426
|
-
|
|
76427
|
-
|
|
76575
|
+
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and94(
|
|
76576
|
+
eq111(siteCrawlEdges.projectId, scope.projectId),
|
|
76577
|
+
eq111(siteCrawlEdges.runId, scope.runId),
|
|
76578
|
+
eq111(siteCrawlEdges.attemptId, scope.attemptId),
|
|
76579
|
+
eq111(siteCrawlEdges.internal, true),
|
|
76428
76580
|
inArray50(siteCrawlEdges.edgeKey, linkKeys)
|
|
76429
76581
|
)).all();
|
|
76430
76582
|
const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
@@ -76598,16 +76750,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76598
76750
|
}
|
|
76599
76751
|
const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
|
|
76600
76752
|
const filters = [
|
|
76601
|
-
|
|
76602
|
-
|
|
76603
|
-
|
|
76753
|
+
eq111(siteCrawlPages.projectId, project.id),
|
|
76754
|
+
eq111(siteCrawlPages.runId, snapshot.runId),
|
|
76755
|
+
eq111(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
76604
76756
|
];
|
|
76605
|
-
if (request.query.nodeKey) filters.push(
|
|
76757
|
+
if (request.query.nodeKey) filters.push(eq111(siteCrawlPages.nodeKey, request.query.nodeKey));
|
|
76606
76758
|
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(
|
|
76759
|
+
if (inventoryEligible != null) filters.push(eq111(siteCrawlPages.inventoryEligible, inventoryEligible));
|
|
76760
|
+
if (request.query.fetchState) filters.push(eq111(siteCrawlPages.fetchState, request.query.fetchState));
|
|
76761
|
+
if (request.query.indexabilityState) filters.push(eq111(siteCrawlPages.indexabilityState, request.query.indexabilityState));
|
|
76762
|
+
if (request.query.auditState) filters.push(eq111(siteCrawlPages.auditState, request.query.auditState));
|
|
76611
76763
|
const urlVersions = parseUrlVersionFilter(request.query.urlVersions);
|
|
76612
76764
|
if (urlVersions === SiteCrawlUrlVersionFilters.exclude) filters.push(isNull17(siteCrawlPages.urlVersionOf));
|
|
76613
76765
|
if (urlVersions === SiteCrawlUrlVersionFilters.only) filters.push(isNotNull10(siteCrawlPages.urlVersionOf));
|
|
@@ -76633,14 +76785,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76633
76785
|
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
76786
|
let healthStateFilter = null;
|
|
76635
76787
|
if (healthState) {
|
|
76636
|
-
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(
|
|
76637
|
-
|
|
76638
|
-
|
|
76639
|
-
|
|
76788
|
+
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and94(
|
|
76789
|
+
eq111(siteCrawlPages.projectId, project.id),
|
|
76790
|
+
eq111(siteCrawlPages.runId, snapshot.runId),
|
|
76791
|
+
eq111(siteCrawlPages.attemptId, snapshot.attemptId),
|
|
76640
76792
|
isNull17(siteCrawlPages.healthState)
|
|
76641
76793
|
)).limit(1).get();
|
|
76642
76794
|
healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
|
|
76643
|
-
if (healthStateFilter === "applied") filters.push(
|
|
76795
|
+
if (healthStateFilter === "applied") filters.push(eq111(siteCrawlPages.healthState, healthState));
|
|
76644
76796
|
}
|
|
76645
76797
|
if (healthStateFilter === "unavailable-legacy-scan") {
|
|
76646
76798
|
return {
|
|
@@ -76657,7 +76809,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76657
76809
|
pages: []
|
|
76658
76810
|
};
|
|
76659
76811
|
}
|
|
76660
|
-
const where =
|
|
76812
|
+
const where = and94(...filters);
|
|
76661
76813
|
const total = app.db.select({ value: count8() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
|
|
76662
76814
|
const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
|
|
76663
76815
|
const nextOffset = offset + rows.length;
|
|
@@ -76692,10 +76844,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76692
76844
|
url: siteCrawlPages.url,
|
|
76693
76845
|
inventoryEligible: siteCrawlPages.inventoryEligible,
|
|
76694
76846
|
fetchState: siteCrawlPages.fetchState
|
|
76695
|
-
}).from(siteCrawlPages).where(
|
|
76696
|
-
|
|
76697
|
-
|
|
76698
|
-
|
|
76847
|
+
}).from(siteCrawlPages).where(and94(
|
|
76848
|
+
eq111(siteCrawlPages.projectId, project.id),
|
|
76849
|
+
eq111(siteCrawlPages.runId, snapshot.runId),
|
|
76850
|
+
eq111(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
76699
76851
|
)).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
|
|
76700
76852
|
if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
|
|
76701
76853
|
throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
|
|
@@ -76777,17 +76929,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76777
76929
|
};
|
|
76778
76930
|
}
|
|
76779
76931
|
const filters = [
|
|
76780
|
-
|
|
76781
|
-
|
|
76782
|
-
|
|
76783
|
-
|
|
76932
|
+
eq111(siteCrawlEdges.projectId, project.id),
|
|
76933
|
+
eq111(siteCrawlEdges.runId, snapshot.runId),
|
|
76934
|
+
eq111(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
76935
|
+
eq111(siteCrawlEdges.internal, true),
|
|
76784
76936
|
linkKindFilter(linkKind)
|
|
76785
76937
|
];
|
|
76786
|
-
if (request.query.sourceUrl) filters.push(
|
|
76787
|
-
if (request.query.targetUrl) filters.push(
|
|
76938
|
+
if (request.query.sourceUrl) filters.push(eq111(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
|
|
76939
|
+
if (request.query.targetUrl) filters.push(eq111(siteCrawlEdges.targetUrl, request.query.targetUrl));
|
|
76788
76940
|
const followable = parseBoolean2(request.query.followable);
|
|
76789
|
-
if (followable != null) filters.push(
|
|
76790
|
-
const where =
|
|
76941
|
+
if (followable != null) filters.push(eq111(siteCrawlEdges.followable, followable));
|
|
76942
|
+
const where = and94(...filters);
|
|
76791
76943
|
const total = app.db.select({ value: count8() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
|
|
76792
76944
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
76793
76945
|
const offset = decodeCursor(request.query.cursor);
|
|
@@ -76848,17 +77000,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76848
77000
|
};
|
|
76849
77001
|
}
|
|
76850
77002
|
const scope = [
|
|
76851
|
-
|
|
76852
|
-
|
|
76853
|
-
|
|
76854
|
-
|
|
77003
|
+
eq111(siteCrawlEdges.projectId, project.id),
|
|
77004
|
+
eq111(siteCrawlEdges.runId, snapshot.runId),
|
|
77005
|
+
eq111(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
77006
|
+
eq111(siteCrawlEdges.internal, true),
|
|
76855
77007
|
linkKindFilter(linkKind)
|
|
76856
77008
|
];
|
|
76857
|
-
const inboundMatch = request.query.nodeKey && request.query.url ? or21(
|
|
76858
|
-
const outboundMatch = request.query.nodeKey && request.query.url ? or21(
|
|
77009
|
+
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);
|
|
77010
|
+
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
77011
|
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(
|
|
77012
|
+
const inboundRows = app.db.select().from(siteCrawlEdges).where(and94(...scope, inboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
77013
|
+
const outboundRows = app.db.select().from(siteCrawlEdges).where(and94(...scope, outboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
76862
77014
|
return {
|
|
76863
77015
|
project: project.name,
|
|
76864
77016
|
hasCrawlData: true,
|
|
@@ -76892,11 +77044,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76892
77044
|
if (!snapshot.attemptId) {
|
|
76893
77045
|
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
77046
|
}
|
|
76895
|
-
const where =
|
|
76896
|
-
|
|
76897
|
-
|
|
76898
|
-
|
|
76899
|
-
|
|
77047
|
+
const where = and94(
|
|
77048
|
+
eq111(siteCrawlFindings.projectId, project.id),
|
|
77049
|
+
eq111(siteCrawlFindings.runId, snapshot.runId),
|
|
77050
|
+
eq111(siteCrawlFindings.attemptId, snapshot.attemptId),
|
|
77051
|
+
eq111(siteCrawlFindings.findingType, "dead-link"),
|
|
76900
77052
|
// Belt and braces on the client-facing surface: a row without a status
|
|
76901
77053
|
// code is not evidence of a broken link, and this response is where such
|
|
76902
77054
|
// a row would be read as one. Migration 140 removed every stored row that
|
|
@@ -76946,16 +77098,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76946
77098
|
createdAt: runs.createdAt,
|
|
76947
77099
|
startedAt: runs.startedAt,
|
|
76948
77100
|
finishedAt: runs.finishedAt
|
|
76949
|
-
}).from(runs).where(
|
|
76950
|
-
|
|
76951
|
-
|
|
77101
|
+
}).from(runs).where(and94(
|
|
77102
|
+
eq111(runs.projectId, project.id),
|
|
77103
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
76952
77104
|
notProbeRun()
|
|
76953
77105
|
)).orderBy(desc50(runs.createdAt), desc50(runs.id)).limit(limit).all();
|
|
76954
77106
|
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
|
-
|
|
77107
|
+
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(
|
|
77108
|
+
eq111(siteCrawlSnapshots.projectId, project.id),
|
|
77109
|
+
eq111(runs.projectId, project.id),
|
|
77110
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
76959
77111
|
inArray50(runs.status, SURFACEABLE_STATUSES),
|
|
76960
77112
|
notProbeRun(),
|
|
76961
77113
|
inArray50(siteCrawlSnapshots.runId, runIds)
|
|
@@ -76972,21 +77124,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
76972
77124
|
});
|
|
76973
77125
|
app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
|
|
76974
77126
|
const project = resolveProject(app.db, request.params.name);
|
|
76975
|
-
const run = app.db.select().from(runs).where(
|
|
76976
|
-
|
|
76977
|
-
|
|
76978
|
-
|
|
77127
|
+
const run = app.db.select().from(runs).where(and94(
|
|
77128
|
+
eq111(runs.id, request.params.runId),
|
|
77129
|
+
eq111(runs.projectId, project.id),
|
|
77130
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
76979
77131
|
notProbeRun()
|
|
76980
77132
|
)).get();
|
|
76981
77133
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
76982
|
-
const attempt = app.db.select().from(siteCrawlAttempts).where(
|
|
76983
|
-
|
|
76984
|
-
|
|
77134
|
+
const attempt = app.db.select().from(siteCrawlAttempts).where(and94(
|
|
77135
|
+
eq111(siteCrawlAttempts.projectId, project.id),
|
|
77136
|
+
eq111(siteCrawlAttempts.runId, run.id)
|
|
76985
77137
|
)).orderBy(desc50(siteCrawlAttempts.attemptNumber), desc50(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
76986
|
-
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(
|
|
76987
|
-
|
|
76988
|
-
|
|
76989
|
-
|
|
77138
|
+
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and94(
|
|
77139
|
+
eq111(siteCrawlGraphLayouts.projectId, project.id),
|
|
77140
|
+
eq111(siteCrawlGraphLayouts.runId, run.id),
|
|
77141
|
+
eq111(siteCrawlGraphLayouts.attemptId, attempt.id)
|
|
76990
77142
|
)).get() : void 0;
|
|
76991
77143
|
const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
|
|
76992
77144
|
return {
|
|
@@ -77017,18 +77169,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
77017
77169
|
};
|
|
77018
77170
|
});
|
|
77019
77171
|
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(
|
|
77172
|
+
const project = tx.select().from(projects).where(eq111(projects.name, request.params.name)).get();
|
|
77021
77173
|
if (!project) throw notFound("Project", request.params.name);
|
|
77022
|
-
const run = tx.select().from(runs).where(
|
|
77023
|
-
|
|
77024
|
-
|
|
77025
|
-
|
|
77174
|
+
const run = tx.select().from(runs).where(and94(
|
|
77175
|
+
eq111(runs.id, request.params.runId),
|
|
77176
|
+
eq111(runs.projectId, project.id),
|
|
77177
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
77026
77178
|
notProbeRun()
|
|
77027
77179
|
)).get();
|
|
77028
77180
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
77029
|
-
const attempt = tx.select().from(siteCrawlAttempts).where(
|
|
77030
|
-
|
|
77031
|
-
|
|
77181
|
+
const attempt = tx.select().from(siteCrawlAttempts).where(and94(
|
|
77182
|
+
eq111(siteCrawlAttempts.projectId, project.id),
|
|
77183
|
+
eq111(siteCrawlAttempts.runId, run.id)
|
|
77032
77184
|
)).orderBy(desc50(siteCrawlAttempts.attemptNumber), desc50(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
77033
77185
|
const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
|
|
77034
77186
|
const base = {
|
|
@@ -77042,11 +77194,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
77042
77194
|
if (!attempt) {
|
|
77043
77195
|
return { ...base, pagesAudited: 0, examples: [] };
|
|
77044
77196
|
}
|
|
77045
|
-
const auditedWhere =
|
|
77046
|
-
|
|
77047
|
-
|
|
77048
|
-
|
|
77049
|
-
|
|
77197
|
+
const auditedWhere = and94(
|
|
77198
|
+
eq111(siteCrawlPages.projectId, project.id),
|
|
77199
|
+
eq111(siteCrawlPages.runId, run.id),
|
|
77200
|
+
eq111(siteCrawlPages.attemptId, attempt.id),
|
|
77201
|
+
eq111(siteCrawlPages.auditState, "success")
|
|
77050
77202
|
);
|
|
77051
77203
|
const pagesAudited = tx.select({ value: count8() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
|
|
77052
77204
|
if (state !== "collecting") {
|
|
@@ -77057,10 +77209,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
77057
77209
|
url: siteCrawlPages.url,
|
|
77058
77210
|
auditScore: siteCrawlPages.auditScore,
|
|
77059
77211
|
auditFields: siteCrawlPages.auditFields
|
|
77060
|
-
}).from(siteCrawlPages).where(
|
|
77212
|
+
}).from(siteCrawlPages).where(and94(
|
|
77061
77213
|
auditedWhere,
|
|
77062
77214
|
isNotNull10(siteCrawlPages.auditScore),
|
|
77063
|
-
|
|
77215
|
+
lt19(siteCrawlPages.auditScore, 70)
|
|
77064
77216
|
)).orderBy(asc27(siteCrawlPages.auditScore), asc27(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
|
|
77065
77217
|
const examples = candidates.flatMap((candidate) => {
|
|
77066
77218
|
if (candidate.auditScore == null) return [];
|
|
@@ -77093,12 +77245,12 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
77093
77245
|
status: runs.status,
|
|
77094
77246
|
identityKey: siteCrawlRunRequests.identityKey,
|
|
77095
77247
|
effectiveOptions: siteCrawlRunRequests.effectiveOptions
|
|
77096
|
-
}).from(runs).leftJoin(siteCrawlRunRequests,
|
|
77097
|
-
|
|
77098
|
-
|
|
77099
|
-
)).where(
|
|
77100
|
-
|
|
77101
|
-
|
|
77248
|
+
}).from(runs).leftJoin(siteCrawlRunRequests, and94(
|
|
77249
|
+
eq111(siteCrawlRunRequests.projectId, runs.projectId),
|
|
77250
|
+
eq111(siteCrawlRunRequests.runId, runs.id)
|
|
77251
|
+
)).where(and94(
|
|
77252
|
+
eq111(runs.projectId, project.id),
|
|
77253
|
+
eq111(runs.kind, RunKinds["site-audit"]),
|
|
77102
77254
|
inArray50(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
77103
77255
|
)).get();
|
|
77104
77256
|
if (existing) {
|
|
@@ -77153,7 +77305,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
77153
77305
|
}
|
|
77154
77306
|
|
|
77155
77307
|
// ../api-routes/src/trends.ts
|
|
77156
|
-
import { and as
|
|
77308
|
+
import { and as and95, asc as asc28, eq as eq112 } from "drizzle-orm";
|
|
77157
77309
|
function parseSurface(value) {
|
|
77158
77310
|
const parsed = trendSurfaceSchema.safeParse(value);
|
|
77159
77311
|
if (!parsed.success) {
|
|
@@ -77209,8 +77361,8 @@ async function trendsRoutes(app) {
|
|
|
77209
77361
|
function readSurface(app, projectId, surface) {
|
|
77210
77362
|
switch (surface) {
|
|
77211
77363
|
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(
|
|
77364
|
+
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();
|
|
77365
|
+
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
77366
|
const coverage = (key, label, rows) => ({
|
|
77215
77367
|
key,
|
|
77216
77368
|
label,
|
|
@@ -77222,7 +77374,7 @@ function readSurface(app, projectId, surface) {
|
|
|
77222
77374
|
return [coverage("google", "Google", google), coverage("bing", "Bing", bing)];
|
|
77223
77375
|
}
|
|
77224
77376
|
case TrendSurfaces["site-health"]: {
|
|
77225
|
-
const rows = app.db.select({ auditedAt: siteAuditSnapshots.auditedAt, aggregateScore: siteAuditSnapshots.aggregateScore, pagesAudited: siteAuditSnapshots.pagesAudited }).from(siteAuditSnapshots).innerJoin(runs,
|
|
77377
|
+
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
77378
|
return [{
|
|
77227
77379
|
key: "site-health",
|
|
77228
77380
|
label: "Site Health",
|
|
@@ -77234,7 +77386,7 @@ function readSurface(app, projectId, surface) {
|
|
|
77234
77386
|
}
|
|
77235
77387
|
case TrendSurfaces.backlinks: {
|
|
77236
77388
|
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(
|
|
77389
|
+
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
77390
|
const filtered = totals.get(row.release);
|
|
77239
77391
|
const known = (filtered?.domainRows ?? 0) > 0 || row.totalLinkingDomains === 0;
|
|
77240
77392
|
const windowEnd = releaseWindowEndMonth(row.release);
|
|
@@ -77264,7 +77416,7 @@ function readSurface(app, projectId, surface) {
|
|
|
77264
77416
|
// ../api-routes/src/research.ts
|
|
77265
77417
|
import crypto60 from "crypto";
|
|
77266
77418
|
import { z as z7 } from "zod";
|
|
77267
|
-
import { and as
|
|
77419
|
+
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
77420
|
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
77421
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
77270
77422
|
async function researchRoutes(app, opts) {
|
|
@@ -77283,7 +77435,7 @@ async function researchRoutes(app, opts) {
|
|
|
77283
77435
|
const input = parsed.data;
|
|
77284
77436
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
77285
77437
|
const requestHash2 = directResearchRequestHash(input);
|
|
77286
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
77438
|
+
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
77439
|
if (existingReceipt) {
|
|
77288
77440
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
77289
77441
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -77318,7 +77470,7 @@ async function researchRoutes(app, opts) {
|
|
|
77318
77470
|
const initiatedBy = researchPrincipal(request);
|
|
77319
77471
|
const decision = app.db.transaction((tx) => {
|
|
77320
77472
|
if (input.idempotencyKey) {
|
|
77321
|
-
const existing = tx.select().from(researchRuns).where(
|
|
77473
|
+
const existing = tx.select().from(researchRuns).where(and96(eq113(researchRuns.projectId, project.id), eq113(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
77322
77474
|
if (existing) {
|
|
77323
77475
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
77324
77476
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -77326,10 +77478,10 @@ async function researchRoutes(app, opts) {
|
|
|
77326
77478
|
}
|
|
77327
77479
|
if (initiatedBy?.limited) {
|
|
77328
77480
|
const { start, end, date } = utcDayBounds(now);
|
|
77329
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
77330
|
-
|
|
77481
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and96(
|
|
77482
|
+
eq113(researchRuns.projectId, project.id),
|
|
77331
77483
|
gte33(researchRuns.createdAt, start),
|
|
77332
|
-
|
|
77484
|
+
lt20(researchRuns.createdAt, end),
|
|
77333
77485
|
sql46`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
77334
77486
|
)).get()?.value ?? 0;
|
|
77335
77487
|
if (used >= viewerDailyRunLimit) {
|
|
@@ -77358,15 +77510,15 @@ async function researchRoutes(app, opts) {
|
|
|
77358
77510
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
77359
77511
|
const requestHash2 = researchBatchRequestHash(input);
|
|
77360
77512
|
const receiptKeys = input.runs.map((_run, index) => batchChildReceiptKey(input.idempotencyKey, index));
|
|
77361
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
77362
|
-
|
|
77363
|
-
|
|
77513
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and96(
|
|
77514
|
+
eq113(researchRuns.projectId, project.id),
|
|
77515
|
+
eq113(researchRuns.idempotencyKey, receiptKeys[0])
|
|
77364
77516
|
)).get();
|
|
77365
77517
|
if (firstReceipt) {
|
|
77366
77518
|
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
|
-
|
|
77519
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and96(
|
|
77520
|
+
eq113(researchRuns.projectId, project.id),
|
|
77521
|
+
eq113(researchRuns.idempotencyKey, key)
|
|
77370
77522
|
)).get());
|
|
77371
77523
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
77372
77524
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -77382,10 +77534,10 @@ async function researchRoutes(app, opts) {
|
|
|
77382
77534
|
const decision = app.db.transaction((tx) => {
|
|
77383
77535
|
if (initiatedBy?.limited) {
|
|
77384
77536
|
const { start, end, date } = utcDayBounds(now);
|
|
77385
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
77386
|
-
|
|
77537
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and96(
|
|
77538
|
+
eq113(researchRuns.projectId, project.id),
|
|
77387
77539
|
gte33(researchRuns.createdAt, start),
|
|
77388
|
-
|
|
77540
|
+
lt20(researchRuns.createdAt, end),
|
|
77389
77541
|
sql46`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
77390
77542
|
)).get()?.value ?? 0;
|
|
77391
77543
|
if (used + prepared.length > viewerDailyRunLimit) throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
|
|
@@ -77437,9 +77589,9 @@ async function researchRoutes(app, opts) {
|
|
|
77437
77589
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
77438
77590
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
77439
77591
|
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(
|
|
77592
|
+
const rows = app.db.select().from(researchRuns).where(and96(
|
|
77593
|
+
eq113(researchRuns.projectId, project.id),
|
|
77594
|
+
cursor ? or22(lt20(researchRuns.createdAt, cursor.createdAt), and96(eq113(researchRuns.createdAt, cursor.createdAt), lt20(researchRuns.id, cursor.id))) : void 0
|
|
77443
77595
|
)).orderBy(desc51(researchRuns.createdAt), desc51(researchRuns.id)).limit(limit + 1).all();
|
|
77444
77596
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
77445
77597
|
const last = runs2.at(-1);
|
|
@@ -77589,9 +77741,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
77589
77741
|
return stored;
|
|
77590
77742
|
}
|
|
77591
77743
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
77592
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
77593
|
-
|
|
77594
|
-
|
|
77744
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and96(
|
|
77745
|
+
eq113(measurementQueryTemplates.projectId, projectId),
|
|
77746
|
+
eq113(measurementQueryTemplates.id, selection.templateId)
|
|
77595
77747
|
)).get();
|
|
77596
77748
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
77597
77749
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -77600,7 +77752,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
77600
77752
|
}
|
|
77601
77753
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
77602
77754
|
if (bindingLocation) {
|
|
77603
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
77755
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq113(projects.id, projectId)).get();
|
|
77604
77756
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
77605
77757
|
}
|
|
77606
77758
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -77613,9 +77765,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
77613
77765
|
return queries2.map((query2) => classifier?.classify(query2) ?? null);
|
|
77614
77766
|
}
|
|
77615
77767
|
function getDetail(app, projectId, id) {
|
|
77616
|
-
const row = app.db.select().from(researchRuns).where(
|
|
77768
|
+
const row = app.db.select().from(researchRuns).where(and96(eq113(researchRuns.id, id), eq113(researchRuns.projectId, projectId))).get();
|
|
77617
77769
|
if (!row) throw notFound("Research run", id);
|
|
77618
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
77770
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq113(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
77619
77771
|
return { ...serializeRun(row), queries: queries2 };
|
|
77620
77772
|
}
|
|
77621
77773
|
function serializeRun(row) {
|
|
@@ -77670,7 +77822,7 @@ function utcDayBounds(now) {
|
|
|
77670
77822
|
|
|
77671
77823
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
77672
77824
|
import crypto61 from "crypto";
|
|
77673
|
-
import { and as
|
|
77825
|
+
import { and as and97, eq as eq114 } from "drizzle-orm";
|
|
77674
77826
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
77675
77827
|
return db.transaction((tx) => {
|
|
77676
77828
|
const run = tx.select({
|
|
@@ -77678,7 +77830,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
77678
77830
|
trigger: runs.trigger,
|
|
77679
77831
|
status: runs.status,
|
|
77680
77832
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
77681
|
-
}).from(runs).where(
|
|
77833
|
+
}).from(runs).where(and97(eq114(runs.projectId, input.projectId), eq114(runs.id, input.runId))).get();
|
|
77682
77834
|
if (!run) throw notFound("Run", input.runId);
|
|
77683
77835
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
77684
77836
|
return null;
|
|
@@ -77699,7 +77851,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
77699
77851
|
throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
|
|
77700
77852
|
}
|
|
77701
77853
|
}
|
|
77702
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
77854
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and97(eq114(simpleMeasurementDefinitions.projectId, input.projectId), eq114(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
77703
77855
|
if (existing) {
|
|
77704
77856
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
77705
77857
|
const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
|
|
@@ -77725,15 +77877,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
77725
77877
|
if (run.status !== RunStatuses.running) {
|
|
77726
77878
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
77727
77879
|
}
|
|
77728
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
77880
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq114(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
77729
77881
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
77730
77882
|
}
|
|
77731
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
77883
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq114(queries.projectId, input.projectId)).all().map((query2) => query2.id));
|
|
77732
77884
|
if (definition2.queries.some((query2) => !projectQueryIds.has(query2.queryId))) {
|
|
77733
77885
|
throw validationError("Every captured query must belong to the run project.");
|
|
77734
77886
|
}
|
|
77735
77887
|
if (definition2.competitors !== void 0) {
|
|
77736
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
77888
|
+
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
77889
|
const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
|
|
77738
77890
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
77739
77891
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|