ghost 6.17.2 → 6.18.1
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/components/{tryghost-i18n-6.17.2.tgz → tryghost-i18n-6.18.1.tgz} +0 -0
- package/components/{tryghost-parse-email-address-6.17.2.tgz → tryghost-parse-email-address-6.18.1.tgz} +0 -0
- package/core/built/admin/assets/{PolarAngleAxis-B8JrAxf_.js → PolarAngleAxis-Dod0DwfL.js} +1 -1
- package/core/built/admin/assets/{_baseAssignValue-Bbalamaq.js → _baseAssignValue-DnkbkowM.js} +1 -1
- package/core/built/admin/assets/{a-large-small-CgWPeKpi.js → a-large-small-C5mgFBRg.js} +1 -1
- package/core/built/admin/assets/activitypub/activitypub.js +1 -1
- package/core/built/admin/assets/activitypub/{at-sign-Fsk3x72r.mjs → at-sign-BR2C2gdz.mjs} +2 -2
- package/core/built/admin/assets/activitypub/{avatar-flipboard-C10JfFS_.mjs → avatar-flipboard-CJV3KhLU.mjs} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-C1xeGSL6.mjs → bluesky-sharing-DY4KCGPZ.mjs} +4 -4
- package/core/built/admin/assets/activitypub/{copy-C1fElSkQ.mjs → copy-CBFwbAdE.mjs} +2 -2
- package/core/built/admin/assets/activitypub/{deleted-feed-item-Bun4tY2_.mjs → deleted-feed-item-DOm9GxTQ.mjs} +2 -2
- package/core/built/admin/assets/activitypub/{edit-profile-CYh00FZ7.mjs → edit-profile-CcbnZWkM.mjs} +3 -3
- package/core/built/admin/assets/activitypub/{feed-BxUqmcN9.mjs → feed-BHiZU0Q_.mjs} +4 -4
- package/core/built/admin/assets/activitypub/{hash-CNgwAx-U.mjs → hash-Csjloy5t.mjs} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-DqNqII4a.mjs → inbox-DGKbBux9.mjs} +2 -2
- package/core/built/admin/assets/activitypub/{index-CONoLlDU.mjs → index-Bt_qFJNY.mjs} +2 -2
- package/core/built/admin/assets/activitypub/{index--Q6orQkb.mjs → index-CCYuVHjm.mjs} +21 -21
- package/core/built/admin/assets/activitypub/{index-jhjmoHwu.mjs → index-CTV39jCH.mjs} +2 -2
- package/core/built/admin/assets/activitypub/{index-Dvh9q3jy.mjs → index-ClNx7qzi.mjs} +4 -4
- package/core/built/admin/assets/activitypub/{index-C9pnotJK.mjs → index-D6Y6ywyl.mjs} +6 -6
- package/core/built/admin/assets/activitypub/{index-BueIufRq.mjs → index-YLvIz6pJ.mjs} +5 -5
- package/core/built/admin/assets/activitypub/{index-C3KJXzZE.mjs → index-tWANcGjH.mjs} +7 -7
- package/core/built/admin/assets/activitypub/{index-CJJXnqq1.mjs → index-vVqFEPIX.mjs} +3 -3
- package/core/built/admin/assets/activitypub/{moderation-CYhwUFi2.mjs → moderation-CUGqRfT1.mjs} +3 -3
- package/core/built/admin/assets/activitypub/{note-COVa8CMw.mjs → note-DMy39JHG.mjs} +4 -4
- package/core/built/admin/assets/activitypub/{reply-BHpKVBxx.mjs → reply-BboFcDsv.mjs} +2 -2
- package/core/built/admin/assets/activitypub/{separator-DP7q5sFH.mjs → separator-DBvIXQnV.mjs} +2 -2
- package/core/built/admin/assets/activitypub/{settings-3n7zo_3K.mjs → settings-D4qRhs9E.mjs} +3 -3
- package/core/built/admin/assets/activitypub/{step-1-BmUukywZ.mjs → step-1-C-ubwwIT.mjs} +3 -3
- package/core/built/admin/assets/activitypub/{step-2-C--I3xxp.mjs → step-2-Dsd2XY1_.mjs} +5 -5
- package/core/built/admin/assets/activitypub/{step-3-0Deh5N9c.mjs → step-3-BHz3ow0t.mjs} +7 -7
- package/core/built/admin/assets/activitypub/{tabs-D_vmoLBo.mjs → tabs-s_P4XeyF.mjs} +3 -3
- package/core/built/admin/assets/activitypub/{topic-filter-DJMrhH-c.mjs → topic-filter-CCf15CR-.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
- package/core/built/admin/assets/admin-x-settings/{code-editor-view-BzvYs1fs.mjs → code-editor-view-BWi3-ftq.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-DdMo_pvD.mjs → index-B3PtvbUw.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-DYEF3NiM.mjs → index-Did70N9h.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-BXChUyr6.mjs → index-JaMlaX9G.mjs} +30 -19
- package/core/built/admin/assets/admin-x-settings/{modals-Ck3AHDx3.mjs → modals-Bwrf9ptQ.mjs} +8060 -8067
- package/core/built/admin/assets/{at-sign-BottTo2X.js → at-sign-Bz-SU-S_.js} +1 -1
- package/core/built/admin/assets/{audience-CcGOt5Fb.js → audience-DmVdEmIe.js} +1 -1
- package/core/built/admin/assets/{avatar-flipboard-MxW2k124.js → avatar-flipboard-C7sxDVEM.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-BBmDBlxr.js → bluesky-sharing-DJniSF6N.js} +1 -1
- package/core/built/admin/assets/{chart-pimL5Chk.js → chart-mYz3IJwm.js} +1 -1
- package/core/built/admin/assets/{chunk.524.9a8f5123fd3ecf3e4451.js → chunk.524.9d300778a63b42b0de62.js} +7 -7
- package/core/built/admin/assets/{chunk.582.41886d06af2895d571bd.js → chunk.582.2e363cd976d9bba998b9.js} +10 -10
- package/core/built/admin/assets/{code-editor-view-tCZOpc_h.js → code-editor-view-BIQWFJ01.js} +1 -1
- package/core/built/admin/assets/comments-B_-hdjc6.js +1 -0
- package/core/built/admin/assets/{copy-aC0WUVtq.js → copy-Bpq3uhnh.js} +1 -1
- package/core/built/admin/assets/{data-list-BOdyZGWI.js → data-list-BYNMbRIq.js} +1 -1
- package/core/built/admin/assets/{deleted-feed-item-BnUJ8bWP.js → deleted-feed-item-B_AwUKZy.js} +1 -1
- package/core/built/admin/assets/{edit-profile-CEgPhWMJ.js → edit-profile-DZo2ZcOu.js} +1 -1
- package/core/built/admin/assets/{empty-indicator-CR_9BHAH.js → empty-indicator-Bn5wG9-T.js} +1 -1
- package/core/built/admin/assets/{en-ajofhc7o.js → en-D4zIrMLN.js} +1 -1
- package/core/built/admin/assets/{feed-Ci-ODCHE.js → feed-Bgq4RarU.js} +1 -1
- package/core/built/admin/assets/{filters-CmJJOv3I.js → filters-CpVwu1Gk.js} +1 -1
- package/core/built/admin/assets/{gh-chart-hn7wEnik.js → gh-chart-Br7NXn_b.js} +1 -1
- package/core/built/admin/assets/{ghost-78b420974adc056bf1c9570275a46535.css → ghost-3c48d37b32f4fcd7eb15cfd739905a03.css} +1 -1
- package/core/built/admin/assets/{ghost-a9268074d0a9b02198594a05ecd7a626.js → ghost-98a3c5fd6235ebd344594f491cb6d17b.js} +241 -246
- package/core/built/admin/assets/{ghost-dark-12abca67e79c76fe6bfed6cb81ba2a53.css → ghost-dark-0100fe3ec7b4c3f6d8d043c904254a63.css} +1 -1
- package/core/built/admin/assets/{growth-CQceZQ1g.js → growth-CiPDnWxJ.js} +1 -1
- package/core/built/admin/assets/{hash-BcaWmkw5.js → hash-Bc1e38oH.js} +1 -1
- package/core/built/admin/assets/{inbox-D9hID-rO.js → inbox-BcZNBNhk.js} +1 -1
- package/core/built/admin/assets/{index-d_UH71zm.js → index-BFEx_ouC.js} +1 -1
- package/core/built/admin/assets/{index-kXF42nga.js → index-B_7QsC5T.js} +1 -1
- package/core/built/admin/assets/{index-CZ3lARQ2.js → index-Bm5ZeLtt.js} +1 -1
- package/core/built/admin/assets/{index-DJDgMk-T.js → index-BpcL7RmI.js} +2 -2
- package/core/built/admin/assets/{index-g4BWcHhl.js → index-Brg4tecQ.js} +1 -1
- package/core/built/admin/assets/{index-f5Lv9kLb.js → index-CMTzNTew.js} +29 -29
- package/core/built/admin/assets/{index-Cl2eSBwg.js → index-CTfJflJ2.js} +1 -1
- package/core/built/admin/assets/{index-BJS8H75W.js → index-Cn32t_gv.js} +1 -1
- package/core/built/admin/assets/{index-D8syEvSm.js → index-CsidX7Si.js} +1 -1
- package/core/built/admin/assets/{index-DO76vL0R.js → index-D212VvOz.js} +1 -1
- package/core/built/admin/assets/{index-B1rKIiOr.js → index-D67LJ_H4.js} +1 -1
- package/core/built/admin/assets/{index-BmK6ThYl.js → index-DU3bv9jz.js} +1 -1
- package/core/built/admin/assets/{index-DYM1MbXR.js → index-Dv13wKfX.js} +1 -1
- package/core/built/admin/assets/{index-Do091GLu.js → index-ETsoQLbU.js} +1 -1
- package/core/built/admin/assets/{index-rluYcfd-.js → index-ZYDRMgcT.js} +1 -1
- package/core/built/admin/assets/{koenig-lexical-D2XHvS2J.js → koenig-lexical-DZUWzN0P.js} +1 -1
- package/core/built/admin/assets/{kpi-card-Dga9rFZ5.js → kpi-card-DENsK2xK.js} +1 -1
- package/core/built/admin/assets/{kpis-BbnqD1za.js → kpis-CDrs2iS1.js} +1 -1
- package/core/built/admin/assets/{label-D8WHEOe0.js → label-BznQtEEo.js} +1 -1
- package/core/built/admin/assets/{links-dgwx7ko1.js → links-lpAC3T1p.js} +1 -1
- package/core/built/admin/assets/{lucide-react-BARFK2PZ.js → lucide-react-CBigk-fq.js} +1 -1
- package/core/built/admin/assets/{main-layout-CcjtCuF_.js → main-layout-_SYQRjIl.js} +1 -1
- package/core/built/admin/assets/{message-square-text-BxquqPhA.js → message-square-text-cV8O_qKq.js} +1 -1
- package/core/built/admin/assets/{minus-Ziy4LSYD.js → minus-NvnQTlW7.js} +1 -1
- package/core/built/admin/assets/{modals-fMQzR7L6.js → modals-XRSkribf.js} +3 -3
- package/core/built/admin/assets/{moderation-Ci9UNwiZ.js → moderation-BQp1GEWG.js} +1 -1
- package/core/built/admin/assets/{newsletter-DnrS7Fvi.js → newsletter-foM6KNNV.js} +1 -1
- package/core/built/admin/assets/{newsletters-D78pgnas.js → newsletters-BexdXUhn.js} +1 -1
- package/core/built/admin/assets/{note-BAfNweZN.js → note-DuaUGOeZ.js} +1 -1
- package/core/built/admin/assets/{overview-CWQPNOF0.js → overview-DgKBNqyc.js} +1 -1
- package/core/built/admin/assets/{pagemenu-DjSqq3Wu.js → pagemenu-CZyroidv.js} +1 -1
- package/core/built/admin/assets/{post-analytics-c8kRV0YB.js → post-analytics-DLK2SOSQ.js} +1 -1
- package/core/built/admin/assets/{post-analytics-context-CEqWyqgm.js → post-analytics-context-CF7C67-0.js} +1 -1
- package/core/built/admin/assets/{post-analytics-header-CpPPBAz4.js → post-analytics-header-7GtJCx0W.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-Dln0kZhn.js → post-share-modal-D8R7DUZP.js} +1 -1
- package/core/built/admin/assets/posts/{comments-CHEqbZPq.mjs → comments-Bn_vn_sb.mjs} +434 -407
- package/core/built/admin/assets/posts/{dialog-VFomJYla.mjs → dialog-CKPcMrj7.mjs} +3 -3
- package/core/built/admin/assets/posts/{empty-indicator-CT9qQsMO.mjs → empty-indicator-kkrhP6K_.mjs} +2 -2
- package/core/built/admin/assets/posts/{filters-BXCfSKKS.mjs → filters-DHsxxy0F.mjs} +6 -6
- package/core/built/admin/assets/posts/{growth-xMitvyvt.mjs → growth-D7ACh-oR.mjs} +12 -12
- package/core/built/admin/assets/posts/{heading-DIulQwgD.mjs → heading-BrKVbOxA.mjs} +2 -2
- package/core/built/admin/assets/posts/{hooks-BiBMDYOA.mjs → hooks-BBEMuLiW.mjs} +3 -3
- package/core/built/admin/assets/posts/{index-uYyMBANY.mjs → index-C0rRguZx.mjs} +9 -9
- package/core/built/admin/assets/posts/{kpis-DK6j0CJA.mjs → kpis-1o524OIK.mjs} +10 -10
- package/core/built/admin/assets/posts/{links-CBiYZsyk.mjs → links-JpeATx1f.mjs} +4 -4
- package/core/built/admin/assets/posts/{loading-indicator-BPPeKpVb.mjs → loading-indicator-BHAmSf8j.mjs} +3 -3
- package/core/built/admin/assets/posts/{main-layout-f-PcNn-6.mjs → main-layout-BTItAOQE.mjs} +2 -2
- package/core/built/admin/assets/posts/{newsletter-CSOyAfvO.mjs → newsletter-CQdsCEHv.mjs} +13 -13
- package/core/built/admin/assets/posts/{overview-Dve3Uxt4.mjs → overview-sYh4JN1D.mjs} +15 -15
- package/core/built/admin/assets/posts/{post-analytics-CPKxH7yg.mjs → post-analytics-DtLt2SWx.mjs} +6 -6
- package/core/built/admin/assets/posts/{post-analytics-context-B5zQoflZ.mjs → post-analytics-context-CB1yM9fk.mjs} +4 -4
- package/core/built/admin/assets/posts/{post-analytics-header-BBxuaptK.mjs → post-analytics-header-BYaWuL9W.mjs} +9 -9
- package/core/built/admin/assets/posts/{post-share-modal-D_iMXvnI.mjs → post-share-modal-V0HTOBaf.mjs} +4 -4
- package/core/built/admin/assets/posts/{posts-DW6ne0od.mjs → posts-DeQT3knv.mjs} +2 -2
- package/core/built/admin/assets/posts/posts.js +1 -1
- package/core/built/admin/assets/posts/{search-CVlPqGMA.mjs → search-CLjC37AT.mjs} +2 -2
- package/core/built/admin/assets/posts/{separator-YrJkDGVP.mjs → separator-KNoTIaJx.mjs} +5 -5
- package/core/built/admin/assets/posts/{sheet-T5GWxxrn.mjs → sheet-DBg_SCDt.mjs} +3 -3
- package/core/built/admin/assets/posts/{skeleton-qnvlkDy-.mjs → skeleton-DvsoolWu.mjs} +3 -3
- package/core/built/admin/assets/posts/{source-icon-DBU0eZwS.mjs → source-icon-OmRaTU2G.mjs} +3 -3
- package/core/built/admin/assets/posts/{stats-udwJVZP8.mjs → stats-BC2DzntY.mjs} +4 -4
- package/core/built/admin/assets/posts/{table-Cu0BZfBU.mjs → table-CxX9OKAj.mjs} +2 -2
- package/core/built/admin/assets/posts/{tabs-B-RaoD3I.mjs → tabs-B1jw7cBi.mjs} +10 -10
- package/core/built/admin/assets/posts/{tags-DSDGzArh.mjs → tags-BBilTo1a.mjs} +11 -11
- package/core/built/admin/assets/posts/{tags-DByfrDKO.mjs → tags-BitrLT_j.mjs} +2 -2
- package/core/built/admin/assets/posts/{use-infinite-virtual-scroll-B7dD2nWW.mjs → use-infinite-virtual-scroll-DaijA1ao.mjs} +3 -3
- package/core/built/admin/assets/posts/{web-B9wvEtJ_.mjs → web-CKmyC4Xj.mjs} +15 -15
- package/core/built/admin/assets/{posts-C1Eh5Ypq.js → posts-CL9UDYoW.js} +1 -1
- package/core/built/admin/assets/{repeat-CN4fiG5H.js → repeat-DgH39UKE.js} +1 -1
- package/core/built/admin/assets/{reply-BfWgvH7y.js → reply-DAaNxiy8.js} +1 -1
- package/core/built/admin/assets/{select-B_0NBkLz.js → select-Cor2wFXT.js} +1 -1
- package/core/built/admin/assets/{settings-BqaVcJee.js → settings-BeumESEN.js} +5 -5
- package/core/built/admin/assets/{settings-Dk9E7Fqv.js → settings-xRx917Gj.js} +1 -1
- package/core/built/admin/assets/{sort-button-C2M35dLh.js → sort-button-BNW3i4Lb.js} +1 -1
- package/core/built/admin/assets/{source-icon-DkGi13e_.js → source-icon-DvDuzw73.js} +1 -1
- package/core/built/admin/assets/{sprout-BKiwQsGn.js → sprout-C3cc0c-K.js} +1 -1
- package/core/built/admin/assets/{square-BEpRc_fY.js → square-tZp0_n7e.js} +1 -1
- package/core/built/admin/assets/stats/{audience-Dfo7BmNu.mjs → audience-BWqU7WWT.mjs} +3 -3
- package/core/built/admin/assets/stats/{index-DUWoiDE_.mjs → index-CUuQaROI.mjs} +5 -5
- package/core/built/admin/assets/stats/{index-Yo58temM.mjs → index-CcCyLMxL.mjs} +7 -7
- package/core/built/admin/assets/stats/{index-h0rsyxDz.mjs → index-D5mlMG4l.mjs} +8 -8
- package/core/built/admin/assets/stats/{index-_vL3Zubi.mjs → index-DXU2rE9t.mjs} +5 -5
- package/core/built/admin/assets/stats/{index-Banm1wtA.mjs → index-K7ASx7EG.mjs} +6 -6
- package/core/built/admin/assets/stats/{sort-button-i6PS-TUn.mjs → sort-button-CELUx6Zp.mjs} +3 -3
- package/core/built/admin/assets/stats/{stats-CjepXEWS.mjs → stats-d_u_in4l.mjs} +2 -2
- package/core/built/admin/assets/stats/stats.js +2 -2
- package/core/built/admin/assets/stats/{tabs-Q20S1oup.mjs → tabs-3wLZsy0v.mjs} +3 -3
- package/core/built/admin/assets/stats/{url-helpers-DrGoeEH1.mjs → url-helpers-Drq3xg0l.mjs} +4 -4
- package/core/built/admin/assets/stats/{use-growth-stats-BnffY2W3.mjs → use-growth-stats-28Sr42va.mjs} +3 -3
- package/core/built/admin/assets/{stats-CRgy_cWm.js → stats-2Jelnn-Q.js} +1 -1
- package/core/built/admin/assets/{stats-view-CTQ4qO3S.js → stats-view-CESy8ELH.js} +1 -1
- package/core/built/admin/assets/{step-1-CNbdmB8C.js → step-1-DrqdolAh.js} +1 -1
- package/core/built/admin/assets/{step-2-Ddrl2uae.js → step-2-DmEpKck5.js} +1 -1
- package/core/built/admin/assets/{step-3-CbqkEtwh.js → step-3-Bus-0o0n.js} +1 -1
- package/core/built/admin/assets/{table-Dw9xFlFd.js → table-BQUcKHfm.js} +1 -1
- package/core/built/admin/assets/{tabs-DCHagOip.js → tabs-BmdL0X4U.js} +1 -1
- package/core/built/admin/assets/{tags-DV2SSdMK.js → tags-CLxXZlOO.js} +1 -1
- package/core/built/admin/assets/{tags-BsDlfsPN.js → tags-EchqlZUJ.js} +1 -1
- package/core/built/admin/assets/{tiers-tc5tafWV.js → tiers-nCGyTly9.js} +1 -1
- package/core/built/admin/assets/{toggle-group-DS-ZVhCr.js → toggle-group-CM5uf7J1.js} +1 -1
- package/core/built/admin/assets/{topic-filter-BfoEqHRD.js → topic-filter-LTRvZ8aU.js} +1 -1
- package/core/built/admin/assets/{trash-CpnxYen-.js → trash-u5BxolyH.js} +1 -1
- package/core/built/admin/assets/{url-helpers-wKPPeCtD.js → url-helpers-D41fEt51.js} +1 -1
- package/core/built/admin/assets/{use-growth-stats-CbHIETF_.js → use-growth-stats-BJ0O9ewi.js} +1 -1
- package/core/built/admin/assets/{use-infinite-virtual-scroll-DU1EUhyj.js → use-infinite-virtual-scroll-APZWciOk.js} +1 -1
- package/core/built/admin/assets/{use-simple-pagination-X6kmRz2u.js → use-simple-pagination-DVRHeaAR.js} +1 -1
- package/core/built/admin/assets/{user-round-check-DYkrjHxF.js → user-round-check-B6j98D6d.js} +1 -1
- package/core/built/admin/assets/{wallet-cards-CSdWOmJE.js → wallet-cards-KmOh29LP.js} +1 -1
- package/core/built/admin/assets/{web-C39IKnvl.js → web-Cclotbnz.js} +1 -1
- package/core/built/admin/index.html +5 -5
- package/core/frontend/meta/asset-url.js +126 -15
- package/core/frontend/services/asset-hash/index.js +73 -0
- package/core/frontend/services/theme-engine/active.js +4 -2
- package/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js +6 -0
- package/core/server/data/migrations/versions/6.17/2026-02-04-10-42-20-offers-nullable-product-id.js +11 -0
- package/core/server/data/migrations/versions/6.18/2026-02-04-10-42-20-offers-nullable-product-id.js +4 -0
- package/core/server/data/schema/schema.js +1 -1
- package/core/server/data/tinybird/datasources/_mv_daily_pages.datasource +13 -0
- package/core/server/data/tinybird/endpoints/api_top_pages_v3.pipe +113 -0
- package/core/server/data/tinybird/pipes/mv_daily_pages.pipe +23 -0
- package/core/server/data/tinybird/scripts/benchmark-top-pages.sh +186 -0
- package/core/server/data/tinybird/scripts/compare-top-pages.sh +262 -0
- package/core/server/data/tinybird/tests/api_top_pages_v3.yaml +112 -0
- package/core/server/models/automated-email.js +34 -0
- package/core/server/services/koenig/node-renderers/transistor-renderer.js +3 -8
- package/core/server/services/lib/member-signup-contexts.js +23 -0
- package/core/server/services/lib/member-signup-contexts.ts +22 -0
- package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +13 -7
- package/core/server/services/member-welcome-emails/service.js +84 -15
- package/core/server/services/members/members-api/controllers/router-controller.js +14 -0
- package/core/server/services/members/members-api/repositories/member-repository.js +1 -1
- package/core/server/services/members/members-api/services/payments-service.js +5 -0
- package/core/server/services/oembed/oembed-service.js +1 -1
- package/core/server/services/offers/application/offer-mapper.js +6 -7
- package/core/server/services/offers/application/offers-api.js +3 -3
- package/core/server/services/offers/domain/errors/index.js +2 -0
- package/core/server/services/offers/domain/models/offer.js +15 -3
- package/core/server/services/offers/offer-bookshelf-repository.js +4 -5
- package/core/server/services/stats/utils/tinybird.js +5 -3
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +17 -1
- package/core/server/services/stripe/stripe-service.js +8 -0
- package/core/shared/config/defaults.json +6 -0
- package/core/shared/labs.js +3 -3
- package/core/shared/url-utils.js +2 -1
- package/package.json +6 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +23 -2
- package/core/built/admin/assets/comments-OXqhcC0t.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as t,aS as be,aR as xe,by as Se,bz as je,B as oe,bA as _e,bB as Ce,bC as Ne,bD as ve,ch as Le,aj as ye,ai as Te,aU as Ee,r as l,ci as Fe,aT as J,bK as Oe,af as ne,ag as $,cj as ke,bG as De,aP as Ae,u as Ie,aV as Me,aQ as Pe}from"./index-f5Lv9kLb.js";import{S as we,a as Ue,b as Ve,c as Re,d as Be,e as ze,f as Ke}from"./select-B_0NBkLz.js";import{u as H,S as ie,a as re,A as Z,b as z,c as q,U as le}from"./post-analytics-context-CEqWyqgm.js";import{a as $e,F as Ge,b as He}from"./message-square-text-BxquqPhA.js";import{g as We,a as Qe,S as Ye,b as qe}from"./kpis-BbnqD1za.js";import{F as Xe,c as W,e as ce}from"./en-ajofhc7o.js";import{C as ue,a as Je,b as Ze,f as et,c as de,g as tt,u as K,j as ee}from"./pagemenu-DjSqq3Wu.js";import{D as st,h as at,i as te,a as ot,b as nt,c as it,d as rt,e as lt,f as ct,g as ut}from"./data-list-BOdyZGWI.js";import{a as dt,U as mt}from"./wallet-cards-CSdWOmJE.js";import{P as pt,a as ht}from"./post-analytics-header-CpPPBAz4.js";import{F as gt,c as ft}from"./filters-CmJJOv3I.js";import{b as bt,c as xt,F as St,d as jt,S as _t,T as Ct}from"./lucide-react-BARFK2PZ.js";import{E as Nt}from"./empty-indicator-CR_9BHAH.js";import"./posts-C1Eh5Ypq.js";import"./source-icon-DkGi13e_.js";import"./source-utils-B1S3ZHA2.js";import"./gh-chart-hn7wEnik.js";import"./chart-pimL5Chk.js";import"./_baseAssignValue-Bbalamaq.js";import"./tabs-DCHagOip.js";import"./index-DO76vL0R.js";import"./post-share-modal-Dln0kZhn.js";import"./post-helpers-gInwAwEv.js";import"./trash-CpnxYen-.js";import"./sprout-BKiwQsGn.js";import"./a-large-small-CgWPeKpi.js";import"./at-sign-BottTo2X.js";import"./copy-aC0WUVtq.js";import"./hash-BcaWmkw5.js";import"./inbox-D9hID-rO.js";import"./minus-Ziy4LSYD.js";import"./tags-DV2SSdMK.js";import"./square-BEpRc_fY.js";import"./user-round-check-DYkrjHxF.js";import"./repeat-CN4fiG5H.js";import"./reply-BfWgvH7y.js";const se=()=>{const{range:e,setRange:s}=H();return t.jsxs(we,{value:`${e}`,onValueChange:a=>{s(Number(a))},children:[t.jsxs(Ue,{className:"w-auto",children:[t.jsx($e,{className:"mr-2",size:16,strokeWidth:1.5}),t.jsx(Ve,{placeholder:"Select a period"})]}),t.jsx(Re,{align:"end",children:t.jsxs(Be,{children:[t.jsx(ze,{children:"Period"}),Object.values(ie).map(a=>t.jsx(Ke,{value:`${a.value}`,children:a.name},a.value))]})})]})};W.registerLocale(ce);const vt=e=>re[e]||W.getName(e,"en")||"Unknown",Lt=e=>{const s={"UNITED STATES":"US","UNITED STATES OF AMERICA":"US",USA:"US","UNITED KINGDOM":"GB",UK:"GB","GREAT BRITAIN":"GB",NETHERLANDS:"NL"},a=e.toUpperCase();return s[a]||(e.length>2?e.substring(0,2):e)},ae=({tableHeader:e,data:s,onLocationClick:a})=>t.jsxs(st,{children:[e&&t.jsxs(at,{children:[t.jsx(te,{children:"Country"}),t.jsx(te,{children:"Visitors"})]}),t.jsx(ot,{children:s.map(i=>{const o=vt(`${i.location}`),r=a&&i.location!=="Unknown",c=i.location?i.location.toLowerCase():"unknown";return t.jsxs(nt,{className:r?"cursor-pointer":"","data-testid":`location-row-${c}`,onClick:r?()=>a(i.location):void 0,children:[t.jsx(it,{style:{width:`${i.percentage?Math.round(i.percentage*100):0}%`}}),t.jsx(rt,{className:"group-hover/data:max-w-[calc(100%-140px)]",children:t.jsxs("div",{className:"flex items-center space-x-3 overflow-hidden",title:o,children:[t.jsx(Xe,{countryCode:`${Lt(i.location)}`,fallback:t.jsx("span",{className:"flex h-[14px] w-[22px] items-center justify-center rounded-[2px] bg-black text-white",children:t.jsx(Le.SkullAndBones,{className:"size-3"})})}),t.jsx("div",{className:"truncate font-medium",children:o})]})}),t.jsxs(lt,{children:[t.jsx(ct,{children:ye(Number(i.visits))}),t.jsx(ut,{children:Te(i.percentage)})]})]},i.location||"unknown")})})]}),yt=({data:e,isLoading:s,onLocationClick:a})=>{const i=e.slice(0,10);return t.jsx(t.Fragment,{children:s?"":t.jsx(t.Fragment,{children:e&&e.length>0&&t.jsxs(ue,{className:"group/datalist","data-testid":"locations-card",children:[t.jsxs("div",{className:"flex items-center justify-between p-6",children:[t.jsxs(Je,{className:"p-0",children:[t.jsx(Ze,{children:"Locations"}),t.jsx(et,{children:"Where are the readers of this post"})]}),t.jsx(be,{className:"mr-2",children:"Visitors"})]}),t.jsxs(de,{className:"overflow-hidden",children:[t.jsx(xe,{}),t.jsx(ae,{data:i,tableHeader:!1,onLocationClick:a})]}),e.length>10&&t.jsx(tt,{children:t.jsxs(Se,{children:[t.jsx(je,{asChild:!0,children:t.jsxs(oe,{variant:"outline",children:["View all ",t.jsx(dt,{})]})}),t.jsxs(_e,{className:"overflow-y-auto pt-0 sm:max-w-[600px]",children:[t.jsxs(Ce,{className:"sticky top-0 z-40 -mx-6 bg-background/60 p-6 backdrop-blur",children:[t.jsx(Ne,{children:"Top locations"}),t.jsx(ve,{children:"Where are the readers of this post"})]}),t.jsx("div",{className:"group/datalist",children:t.jsx(ae,{data:e,tableHeader:!0,onLocationClick:a})})]})]})})]})})})},me=e=>!e||e.length===0?Z:z.filter(s=>e.includes(s.value)).reduce((s,a)=>s|a.bit,0)||Z,pe=e=>{const s=[];return(e&q.PUBLIC)!==0&&s.push(z[0].value),(e&q.FREE)!==0&&s.push(z[1].value),(e&q.PAID)!==0&&s.push(z[2].value),s.join(",")};W.registerLocale(ce);const Tt=e=>re[e]||W.getName(e,"en")||e,Et=({visits:e})=>t.jsx("span",{className:"order-2 font-mono text-xs text-muted-foreground",children:e.toLocaleString()}),Ft={utm_source:{endpoint:"api_top_utm_sources",valueKey:"utm_source",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_medium:{endpoint:"api_top_utm_mediums",valueKey:"utm_medium",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_campaign:{endpoint:"api_top_utm_campaigns",valueKey:"utm_campaign",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_content:{endpoint:"api_top_utm_contents",valueKey:"utm_content",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_term:{endpoint:"api_top_utm_terms",valueKey:"utm_term",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},source:{endpoint:"api_top_sources",valueKey:"source",transformValue:e=>({value:e||"",label:e||"Direct"})},location:{endpoint:"api_top_locations",valueKey:"location",filterItem(e){const s=String(e.location||"");return s!==""&&!le.includes(s)},transformValue:e=>({value:e,label:Tt(e)})},device:{endpoint:"api_top_devices",valueKey:"device",transformValue:e=>({value:e,label:e==="mobile-ios"?"iOS":e==="mobile-android"?"Android":e==="desktop"?"Desktop":e==="bot"?"Bot":e})}},Ot=(e,s,a)=>{const i={...a};return e.forEach(o=>{if(o.field===s||o.values.length===0)return;const r=o.values[0];o.field!=="audience"&&(o.field==="source"||o.field==="device"||o.field==="location"||o.field.startsWith("utm_"))&&(i[o.field]=r)}),i},O=(e,s=[],a,i={})=>{const{enabled:o=!0}=i,{statsConfig:r,range:c}=H(),{startDate:b,endDate:m,timezone:x}=ne(c),p=Ft[e],d=l.useMemo(()=>{const j=s.find(h=>h.field==="audience");return me(j?.values)},[s]),S=l.useMemo(()=>{const j={site_uuid:r?.id||"",date_from:$(b),date_to:$(m),timezone:x,member_status:pe(d),limit:"50"};return a&&(j.post_uuid=a),Ot(s,e,j)},[r?.id,b,m,x,d,s,e,a]),{data:v,loading:k}=K({endpoint:p?.endpoint||"",statsConfig:r,params:S,enabled:o&&!!p});return{options:l.useMemo(()=>p?(v||[]).filter(h=>p.filterItem?p.filterItem(h):!0).map(h=>{const E=String(h[p.valueKey]??""),M=Number(h.visits)||0,{value:F,label:y}=p.transformValue?p.transformValue(E):{value:E,label:E};return{label:y,value:F,icon:t.jsx(Et,{visits:M})}}):[],[v,p]),loading:k}};function kt({filters:e,onChange:s,...a}){const{appSettings:i}=Ee(),{post:o}=H(),r=o?.uuid,[c,b]=l.useState(null),[m,x]=l.useState(!1);l.useEffect(()=>{const g=window.matchMedia("(max-width: 1024px)"),C=T=>{x(T.matches)};return C(g),g.addEventListener("change",C),()=>g.removeEventListener("change",C)},[]);const p=l.useMemo(()=>{const g=[{value:"undefined",label:"Public visitors",icon:t.jsx(J,{className:"text-gray-700"})},{value:"free",label:"Free members",icon:t.jsx(Oe,{className:"text-green"})},{value:"paid",label:"Paid members",icon:t.jsx(mt,{className:"text-orange"})}];return i?.paidMembersEnabled?g:g.filter(C=>C.value!=="paid")},[i?.paidMembersEnabled]),d=l.useCallback(g=>{const C=c===g,T=e.some(n=>n.field===g);return C||T},[c,e]),{options:S,loading:v}=O("utm_source",e,r,{enabled:d("utm_source")}),{options:k,loading:I}=O("utm_medium",e,r,{enabled:d("utm_medium")}),{options:j,loading:h}=O("utm_campaign",e,r,{enabled:d("utm_campaign")}),{options:E,loading:M}=O("utm_content",e,r,{enabled:d("utm_content")}),{options:F,loading:y}=O("utm_term",e,r,{enabled:d("utm_term")}),{options:R,loading:D}=O("source",e,r,{enabled:d("source")}),{options:U,loading:A}=O("device",e,r,{enabled:d("device")}),{options:B,loading:P}=O("location",e,r,{enabled:d("location")}),_=l.useMemo(()=>[{value:"is",label:"is"}],[]),Q=l.useMemo(()=>{const g=[{key:"utm_source",label:"UTM Source",type:"select",icon:t.jsx(jt,{className:"size-4"}),placeholder:"Select source",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:S,isLoading:v,searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_medium",label:"UTM Medium",type:"select",icon:t.jsx(_t,{className:"size-4"}),placeholder:"Select medium",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:k,isLoading:I,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_campaign",label:"UTM Campaign",type:"select",icon:t.jsx(He,{className:"size-4"}),placeholder:"Select campaign",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:j,isLoading:h,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_content",label:"UTM Content",type:"select",icon:t.jsx(Ct,{className:"size-4"}),placeholder:"Select content",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:E,isLoading:M,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_term",label:"UTM Term",type:"select",icon:t.jsx(ke,{className:"size-4"}),placeholder:"Select term",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:F,isLoading:y,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"}];return[{group:"Basic",fields:[{key:"audience",label:"Audience",type:"multiselect",icon:t.jsx(Fe,{}),options:p.map(({value:C,label:T,icon:n})=>({value:C,label:T,icon:n})),defaultOperator:"is any of",hideOperatorSelect:!0,autoCloseOnSelect:!0},{key:"source",label:"Source",type:"select",icon:t.jsx(J,{className:"size-4"}),placeholder:"Select source",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:R,isLoading:D,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"device",label:"Device",type:"select",icon:t.jsx(bt,{className:"size-4"}),placeholder:"Select device",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:U,isLoading:A,selectedOptionsClassName:"hidden"},{key:"location",label:"Location",type:"select",icon:t.jsx(xt,{className:"size-4"}),placeholder:"Select location",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:B,isLoading:P,searchable:!0,selectedOptionsClassName:"hidden"}]},{group:"UTM parameters",fields:g}]},[S,v,k,I,j,h,E,M,F,y,_,p,R,D,U,A,B,P]),w=e.length>0,Y=l.useCallback(()=>{s&&s([])},[s]);return t.jsxs("div",{className:"mt-3 flex w-full justify-between gap-2 lg:mt-0","data-testid":"stats-filter-container",children:[t.jsx(gt,{addButtonIcon:t.jsx(Ge,{}),addButtonText:w?"Add filter":"Filter",allowMultiple:!1,className:`[&>button]:order-last ${w&&"[&>button]:border-none"}`,fields:Q,filters:e,keyboardShortcut:"f",popoverAlign:m||w?"start":"end",showSearchInput:!1,onActiveFieldChange:b,onChange:s||(()=>{}),...a}),w&&t.jsxs(oe,{className:"hidden font-normal text-muted-foreground lg:flex","data-testid":"stats-filter-clear-button",variant:"ghost",onClick:Y,children:[t.jsx(St,{}),"Clear"]})]})}const G=["audience","source","device","location","utm_source","utm_medium","utm_campaign","utm_content","utm_term"],he="__empty__",ge="%2C";function Dt(e){const s=new URLSearchParams;return e.forEach(a=>{if(G.includes(a.field)&&a.values.length>0){const i=a.values.map(o=>o===""?he:String(o).replace(/,/g,ge)).join(",");s.set(a.field,i)}}),s}const X=new Map;function At(e){return X.has(e)||X.set(e,`url-${e}-${Date.now()}-${Math.random().toString(36).substring(2,11)}`),X.get(e)}function It(e){const s=[],a=new Set(G);return e.forEach((i,o)=>{if(!a.has(o))return;const r=i.split(",").map(c=>c===he?"":c.replace(new RegExp(ge,"g"),","));if(r.length>0){const c=o==="audience"?"is any of":"is";s.push({id:At(o),field:o,operator:c,values:r})}}),s}function Mt(e={}){const[s,a]=De(),{onFiltersChange:i}=e,o=l.useRef(!1),r=l.useMemo(()=>It(s),[s]);l.useEffect(()=>{!o.current&&i&&i(r)},[r,i]);const c=l.useCallback(m=>{o.current=!0;const x=typeof m=="function"?m(r):m,p=Dt(x),d=new URLSearchParams(s);G.forEach(S=>{d.delete(S)}),p.forEach((S,v)=>{d.set(v,S)}),a(d,{replace:!0}),setTimeout(()=>{o.current=!1},0)},[r,s,a]),b=l.useCallback(()=>{o.current=!0;const m=new URLSearchParams(s);G.forEach(x=>{m.delete(x)}),a(m,{replace:!0}),setTimeout(()=>{o.current=!1},0)},[s,a]);return{filters:r,setFilters:c,clearFilters:b}}const xs=()=>{const e=Ae(),{postId:s}=Ie(),{statsConfig:a,isLoading:i,range:o,data:r,post:c,isPostLoading:b}=H(),{filters:m,setFilters:x}=Mt(),p=l.useMemo(()=>{const n=m.find(f=>f.field==="audience");return me(n?.values)},[m]);l.useEffect(()=>{!b&&c?.email_only&&e(`/posts/analytics/${s}`)},[b,c?.email_only,e,s]);const d=l.useMemo(()=>{if(!c?.published_at)return ie.ALL_TIME.value;const n=Me(c.published_at);return o>n?n:o},[c?.published_at,o]),{startDate:S,endDate:v,timezone:k}=ne(d),I=l.useCallback(()=>{const n=document.querySelector(".overflow-y-scroll");n&&n.scrollTo({top:0,behavior:"smooth"})},[]),j=l.useMemo(()=>{const n={};return m.forEach(f=>{const N=f.field,L=f.values;if(N==="audience")return;const u=L&&L.length>0&&L[0]!==null&&L[0]!==void 0,V=N==="source"&&L?.[0]==="";if(u&&(L[0]!==""||V)){const fe=String(L[0]);n[N]=fe}}),n},[m]),h=l.useCallback((n,f)=>{x(N=>N.find(u=>u.field===n)?N.map(u=>u.field===n?{...u,values:[f]}:u):[...N,ft(n,"is",[f])]),I()},[x,I]),E=l.useCallback(n=>h("location",n),[h]),M=l.useCallback(n=>h("source",n),[h]),F=l.useMemo(()=>{const n={site_uuid:a?.id||"",date_from:$(S),date_to:$(v),timezone:k,member_status:pe(p),post_uuid:"",...j};return!b&&c?.uuid?{...n,post_uuid:c.uuid}:n},[b,c,a?.id,S,v,k,p,j]),{data:y,loading:R}=K({endpoint:"api_kpis",statsConfig:a||{id:""},params:F}),{data:D,loading:U}=K({endpoint:"api_top_locations",statsConfig:a||{id:""},params:F}),{data:A,loading:B}=K({endpoint:"api_top_sources",statsConfig:a||{id:""},params:F}),P=l.useMemo(()=>D?.reduce((n,f)=>n+Number(f.visits),0)||0,[D]),_=l.useMemo(()=>A?A.reduce((n,f)=>n+Number(f.visits||0),0):0,[A]),Q=r?.url,w=r?.icon,Y=l.useMemo(()=>{const n=D?.map(u=>({location:String(u.location),visits:Number(u.visits),percentage:P>0?Number(u.visits)/P:0,isUnknown:le.includes(String(u.location))}))||[],f=n.filter(u=>!u.isUnknown),N=n.filter(u=>u.isUnknown),L=N.length>0?[{location:"Unknown",visits:N.reduce((u,V)=>u+V.visits,0),percentage:N.reduce((u,V)=>u+V.percentage,0)}]:[];return[...f,...L]},[D,P]),g=i||b||R||U||B,C=We(y),T=m.length>0;return t.jsxs(t.Fragment,{children:[t.jsxs(pt,{currentTab:"Web",children:[T&&t.jsx(ee,{children:t.jsx(se,{})}),t.jsxs(ee,{className:`${T?"!mt-0 [grid-area:subactions] lg:!mt-[25px]":"[grid-area:actions]"}`,children:[t.jsx(kt,{filters:m,onChange:x}),!T&&t.jsx(se,{})]})]}),t.jsx(ht,{children:g?t.jsx(ue,{className:"size-full",variant:"plain",children:t.jsx(de,{className:"size-full items-center justify-center",children:t.jsx(Pe,{})})}):y&&y.length!==0&&C.visits!=="0"?t.jsxs(t.Fragment,{children:[t.jsx(Qe,{data:y,range:d}),t.jsxs("div",{className:"flex flex-col gap-6 lg:grid lg:grid-cols-2",children:[t.jsx(yt,{data:Y,isLoading:U,onLocationClick:E}),t.jsx(Ye,{data:A,range:d,siteIcon:w,siteUrl:Q,totalVisitors:_,onSourceClick:M})]})]}):t.jsx("div",{className:"grow",children:t.jsx(Nt,{className:"h-full",description:"Try adjusting filters to see more data.",title:`No visitors ${qe(o)}`,children:t.jsx(J,{strokeWidth:1.5})})})})]})};export{xs as default};
|
|
1
|
+
import{j as t,aS as be,aR as xe,by as Se,bz as je,B as oe,bA as _e,bB as Ce,bC as Ne,bD as ve,ch as Le,aj as ye,ai as Te,aU as Ee,r as l,ci as Fe,aT as J,bK as Oe,af as ne,ag as $,cj as ke,bG as De,aP as Ae,u as Ie,aV as Me,aQ as Pe}from"./index-CMTzNTew.js";import{S as we,a as Ue,b as Ve,c as Re,d as Be,e as ze,f as Ke}from"./select-Cor2wFXT.js";import{u as H,S as ie,a as re,A as Z,b as z,c as q,U as le}from"./post-analytics-context-CF7C67-0.js";import{a as $e,F as Ge,b as He}from"./message-square-text-cV8O_qKq.js";import{g as We,a as Qe,S as Ye,b as qe}from"./kpis-CDrs2iS1.js";import{F as Xe,c as W,e as ce}from"./en-D4zIrMLN.js";import{C as ue,a as Je,b as Ze,f as et,c as de,g as tt,u as K,j as ee}from"./pagemenu-CZyroidv.js";import{D as st,h as at,i as te,a as ot,b as nt,c as it,d as rt,e as lt,f as ct,g as ut}from"./data-list-BYNMbRIq.js";import{a as dt,U as mt}from"./wallet-cards-KmOh29LP.js";import{P as pt,a as ht}from"./post-analytics-header-7GtJCx0W.js";import{F as gt,c as ft}from"./filters-CpVwu1Gk.js";import{b as bt,c as xt,F as St,d as jt,S as _t,T as Ct}from"./lucide-react-CBigk-fq.js";import{E as Nt}from"./empty-indicator-Bn5wG9-T.js";import"./posts-CL9UDYoW.js";import"./source-icon-DvDuzw73.js";import"./source-utils-B1S3ZHA2.js";import"./gh-chart-Br7NXn_b.js";import"./chart-mYz3IJwm.js";import"./_baseAssignValue-DnkbkowM.js";import"./tabs-BmdL0X4U.js";import"./index-D212VvOz.js";import"./post-share-modal-D8R7DUZP.js";import"./post-helpers-gInwAwEv.js";import"./trash-u5BxolyH.js";import"./sprout-C3cc0c-K.js";import"./a-large-small-C5mgFBRg.js";import"./at-sign-Bz-SU-S_.js";import"./copy-Bpq3uhnh.js";import"./hash-Bc1e38oH.js";import"./inbox-BcZNBNhk.js";import"./minus-NvnQTlW7.js";import"./tags-CLxXZlOO.js";import"./square-tZp0_n7e.js";import"./user-round-check-B6j98D6d.js";import"./repeat-DgH39UKE.js";import"./reply-DAaNxiy8.js";const se=()=>{const{range:e,setRange:s}=H();return t.jsxs(we,{value:`${e}`,onValueChange:a=>{s(Number(a))},children:[t.jsxs(Ue,{className:"w-auto",children:[t.jsx($e,{className:"mr-2",size:16,strokeWidth:1.5}),t.jsx(Ve,{placeholder:"Select a period"})]}),t.jsx(Re,{align:"end",children:t.jsxs(Be,{children:[t.jsx(ze,{children:"Period"}),Object.values(ie).map(a=>t.jsx(Ke,{value:`${a.value}`,children:a.name},a.value))]})})]})};W.registerLocale(ce);const vt=e=>re[e]||W.getName(e,"en")||"Unknown",Lt=e=>{const s={"UNITED STATES":"US","UNITED STATES OF AMERICA":"US",USA:"US","UNITED KINGDOM":"GB",UK:"GB","GREAT BRITAIN":"GB",NETHERLANDS:"NL"},a=e.toUpperCase();return s[a]||(e.length>2?e.substring(0,2):e)},ae=({tableHeader:e,data:s,onLocationClick:a})=>t.jsxs(st,{children:[e&&t.jsxs(at,{children:[t.jsx(te,{children:"Country"}),t.jsx(te,{children:"Visitors"})]}),t.jsx(ot,{children:s.map(i=>{const o=vt(`${i.location}`),r=a&&i.location!=="Unknown",c=i.location?i.location.toLowerCase():"unknown";return t.jsxs(nt,{className:r?"cursor-pointer":"","data-testid":`location-row-${c}`,onClick:r?()=>a(i.location):void 0,children:[t.jsx(it,{style:{width:`${i.percentage?Math.round(i.percentage*100):0}%`}}),t.jsx(rt,{className:"group-hover/data:max-w-[calc(100%-140px)]",children:t.jsxs("div",{className:"flex items-center space-x-3 overflow-hidden",title:o,children:[t.jsx(Xe,{countryCode:`${Lt(i.location)}`,fallback:t.jsx("span",{className:"flex h-[14px] w-[22px] items-center justify-center rounded-[2px] bg-black text-white",children:t.jsx(Le.SkullAndBones,{className:"size-3"})})}),t.jsx("div",{className:"truncate font-medium",children:o})]})}),t.jsxs(lt,{children:[t.jsx(ct,{children:ye(Number(i.visits))}),t.jsx(ut,{children:Te(i.percentage)})]})]},i.location||"unknown")})})]}),yt=({data:e,isLoading:s,onLocationClick:a})=>{const i=e.slice(0,10);return t.jsx(t.Fragment,{children:s?"":t.jsx(t.Fragment,{children:e&&e.length>0&&t.jsxs(ue,{className:"group/datalist","data-testid":"locations-card",children:[t.jsxs("div",{className:"flex items-center justify-between p-6",children:[t.jsxs(Je,{className:"p-0",children:[t.jsx(Ze,{children:"Locations"}),t.jsx(et,{children:"Where are the readers of this post"})]}),t.jsx(be,{className:"mr-2",children:"Visitors"})]}),t.jsxs(de,{className:"overflow-hidden",children:[t.jsx(xe,{}),t.jsx(ae,{data:i,tableHeader:!1,onLocationClick:a})]}),e.length>10&&t.jsx(tt,{children:t.jsxs(Se,{children:[t.jsx(je,{asChild:!0,children:t.jsxs(oe,{variant:"outline",children:["View all ",t.jsx(dt,{})]})}),t.jsxs(_e,{className:"overflow-y-auto pt-0 sm:max-w-[600px]",children:[t.jsxs(Ce,{className:"sticky top-0 z-40 -mx-6 bg-background/60 p-6 backdrop-blur",children:[t.jsx(Ne,{children:"Top locations"}),t.jsx(ve,{children:"Where are the readers of this post"})]}),t.jsx("div",{className:"group/datalist",children:t.jsx(ae,{data:e,tableHeader:!0,onLocationClick:a})})]})]})})]})})})},me=e=>!e||e.length===0?Z:z.filter(s=>e.includes(s.value)).reduce((s,a)=>s|a.bit,0)||Z,pe=e=>{const s=[];return(e&q.PUBLIC)!==0&&s.push(z[0].value),(e&q.FREE)!==0&&s.push(z[1].value),(e&q.PAID)!==0&&s.push(z[2].value),s.join(",")};W.registerLocale(ce);const Tt=e=>re[e]||W.getName(e,"en")||e,Et=({visits:e})=>t.jsx("span",{className:"order-2 font-mono text-xs text-muted-foreground",children:e.toLocaleString()}),Ft={utm_source:{endpoint:"api_top_utm_sources",valueKey:"utm_source",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_medium:{endpoint:"api_top_utm_mediums",valueKey:"utm_medium",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_campaign:{endpoint:"api_top_utm_campaigns",valueKey:"utm_campaign",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_content:{endpoint:"api_top_utm_contents",valueKey:"utm_content",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},utm_term:{endpoint:"api_top_utm_terms",valueKey:"utm_term",transformValue:e=>({value:e||"(not set)",label:e||"(not set)"})},source:{endpoint:"api_top_sources",valueKey:"source",transformValue:e=>({value:e||"",label:e||"Direct"})},location:{endpoint:"api_top_locations",valueKey:"location",filterItem(e){const s=String(e.location||"");return s!==""&&!le.includes(s)},transformValue:e=>({value:e,label:Tt(e)})},device:{endpoint:"api_top_devices",valueKey:"device",transformValue:e=>({value:e,label:e==="mobile-ios"?"iOS":e==="mobile-android"?"Android":e==="desktop"?"Desktop":e==="bot"?"Bot":e})}},Ot=(e,s,a)=>{const i={...a};return e.forEach(o=>{if(o.field===s||o.values.length===0)return;const r=o.values[0];o.field!=="audience"&&(o.field==="source"||o.field==="device"||o.field==="location"||o.field.startsWith("utm_"))&&(i[o.field]=r)}),i},O=(e,s=[],a,i={})=>{const{enabled:o=!0}=i,{statsConfig:r,range:c}=H(),{startDate:b,endDate:m,timezone:x}=ne(c),p=Ft[e],d=l.useMemo(()=>{const j=s.find(h=>h.field==="audience");return me(j?.values)},[s]),S=l.useMemo(()=>{const j={site_uuid:r?.id||"",date_from:$(b),date_to:$(m),timezone:x,member_status:pe(d),limit:"50"};return a&&(j.post_uuid=a),Ot(s,e,j)},[r?.id,b,m,x,d,s,e,a]),{data:v,loading:k}=K({endpoint:p?.endpoint||"",statsConfig:r,params:S,enabled:o&&!!p});return{options:l.useMemo(()=>p?(v||[]).filter(h=>p.filterItem?p.filterItem(h):!0).map(h=>{const E=String(h[p.valueKey]??""),M=Number(h.visits)||0,{value:F,label:y}=p.transformValue?p.transformValue(E):{value:E,label:E};return{label:y,value:F,icon:t.jsx(Et,{visits:M})}}):[],[v,p]),loading:k}};function kt({filters:e,onChange:s,...a}){const{appSettings:i}=Ee(),{post:o}=H(),r=o?.uuid,[c,b]=l.useState(null),[m,x]=l.useState(!1);l.useEffect(()=>{const g=window.matchMedia("(max-width: 1024px)"),C=T=>{x(T.matches)};return C(g),g.addEventListener("change",C),()=>g.removeEventListener("change",C)},[]);const p=l.useMemo(()=>{const g=[{value:"undefined",label:"Public visitors",icon:t.jsx(J,{className:"text-gray-700"})},{value:"free",label:"Free members",icon:t.jsx(Oe,{className:"text-green"})},{value:"paid",label:"Paid members",icon:t.jsx(mt,{className:"text-orange"})}];return i?.paidMembersEnabled?g:g.filter(C=>C.value!=="paid")},[i?.paidMembersEnabled]),d=l.useCallback(g=>{const C=c===g,T=e.some(n=>n.field===g);return C||T},[c,e]),{options:S,loading:v}=O("utm_source",e,r,{enabled:d("utm_source")}),{options:k,loading:I}=O("utm_medium",e,r,{enabled:d("utm_medium")}),{options:j,loading:h}=O("utm_campaign",e,r,{enabled:d("utm_campaign")}),{options:E,loading:M}=O("utm_content",e,r,{enabled:d("utm_content")}),{options:F,loading:y}=O("utm_term",e,r,{enabled:d("utm_term")}),{options:R,loading:D}=O("source",e,r,{enabled:d("source")}),{options:U,loading:A}=O("device",e,r,{enabled:d("device")}),{options:B,loading:P}=O("location",e,r,{enabled:d("location")}),_=l.useMemo(()=>[{value:"is",label:"is"}],[]),Q=l.useMemo(()=>{const g=[{key:"utm_source",label:"UTM Source",type:"select",icon:t.jsx(jt,{className:"size-4"}),placeholder:"Select source",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:S,isLoading:v,searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_medium",label:"UTM Medium",type:"select",icon:t.jsx(_t,{className:"size-4"}),placeholder:"Select medium",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:k,isLoading:I,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_campaign",label:"UTM Campaign",type:"select",icon:t.jsx(He,{className:"size-4"}),placeholder:"Select campaign",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:j,isLoading:h,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_content",label:"UTM Content",type:"select",icon:t.jsx(Ct,{className:"size-4"}),placeholder:"Select content",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:E,isLoading:M,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"utm_term",label:"UTM Term",type:"select",icon:t.jsx(ke,{className:"size-4"}),placeholder:"Select term",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:F,isLoading:y,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"}];return[{group:"Basic",fields:[{key:"audience",label:"Audience",type:"multiselect",icon:t.jsx(Fe,{}),options:p.map(({value:C,label:T,icon:n})=>({value:C,label:T,icon:n})),defaultOperator:"is any of",hideOperatorSelect:!0,autoCloseOnSelect:!0},{key:"source",label:"Source",type:"select",icon:t.jsx(J,{className:"size-4"}),placeholder:"Select source",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:R,isLoading:D,className:"w-60",popoverContentClassName:"w-60",searchable:!0,selectedOptionsClassName:"hidden"},{key:"device",label:"Device",type:"select",icon:t.jsx(bt,{className:"size-4"}),placeholder:"Select device",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:U,isLoading:A,selectedOptionsClassName:"hidden"},{key:"location",label:"Location",type:"select",icon:t.jsx(xt,{className:"size-4"}),placeholder:"Select location",operators:_,defaultOperator:"is",hideOperatorSelect:!0,options:B,isLoading:P,searchable:!0,selectedOptionsClassName:"hidden"}]},{group:"UTM parameters",fields:g}]},[S,v,k,I,j,h,E,M,F,y,_,p,R,D,U,A,B,P]),w=e.length>0,Y=l.useCallback(()=>{s&&s([])},[s]);return t.jsxs("div",{className:"mt-3 flex w-full justify-between gap-2 lg:mt-0","data-testid":"stats-filter-container",children:[t.jsx(gt,{addButtonIcon:t.jsx(Ge,{}),addButtonText:w?"Add filter":"Filter",allowMultiple:!1,className:`[&>button]:order-last ${w&&"[&>button]:border-none"}`,fields:Q,filters:e,keyboardShortcut:"f",popoverAlign:m||w?"start":"end",showSearchInput:!1,onActiveFieldChange:b,onChange:s||(()=>{}),...a}),w&&t.jsxs(oe,{className:"hidden font-normal text-muted-foreground lg:flex","data-testid":"stats-filter-clear-button",variant:"ghost",onClick:Y,children:[t.jsx(St,{}),"Clear"]})]})}const G=["audience","source","device","location","utm_source","utm_medium","utm_campaign","utm_content","utm_term"],he="__empty__",ge="%2C";function Dt(e){const s=new URLSearchParams;return e.forEach(a=>{if(G.includes(a.field)&&a.values.length>0){const i=a.values.map(o=>o===""?he:String(o).replace(/,/g,ge)).join(",");s.set(a.field,i)}}),s}const X=new Map;function At(e){return X.has(e)||X.set(e,`url-${e}-${Date.now()}-${Math.random().toString(36).substring(2,11)}`),X.get(e)}function It(e){const s=[],a=new Set(G);return e.forEach((i,o)=>{if(!a.has(o))return;const r=i.split(",").map(c=>c===he?"":c.replace(new RegExp(ge,"g"),","));if(r.length>0){const c=o==="audience"?"is any of":"is";s.push({id:At(o),field:o,operator:c,values:r})}}),s}function Mt(e={}){const[s,a]=De(),{onFiltersChange:i}=e,o=l.useRef(!1),r=l.useMemo(()=>It(s),[s]);l.useEffect(()=>{!o.current&&i&&i(r)},[r,i]);const c=l.useCallback(m=>{o.current=!0;const x=typeof m=="function"?m(r):m,p=Dt(x),d=new URLSearchParams(s);G.forEach(S=>{d.delete(S)}),p.forEach((S,v)=>{d.set(v,S)}),a(d,{replace:!0}),setTimeout(()=>{o.current=!1},0)},[r,s,a]),b=l.useCallback(()=>{o.current=!0;const m=new URLSearchParams(s);G.forEach(x=>{m.delete(x)}),a(m,{replace:!0}),setTimeout(()=>{o.current=!1},0)},[s,a]);return{filters:r,setFilters:c,clearFilters:b}}const xs=()=>{const e=Ae(),{postId:s}=Ie(),{statsConfig:a,isLoading:i,range:o,data:r,post:c,isPostLoading:b}=H(),{filters:m,setFilters:x}=Mt(),p=l.useMemo(()=>{const n=m.find(f=>f.field==="audience");return me(n?.values)},[m]);l.useEffect(()=>{!b&&c?.email_only&&e(`/posts/analytics/${s}`)},[b,c?.email_only,e,s]);const d=l.useMemo(()=>{if(!c?.published_at)return ie.ALL_TIME.value;const n=Me(c.published_at);return o>n?n:o},[c?.published_at,o]),{startDate:S,endDate:v,timezone:k}=ne(d),I=l.useCallback(()=>{const n=document.querySelector(".overflow-y-scroll");n&&n.scrollTo({top:0,behavior:"smooth"})},[]),j=l.useMemo(()=>{const n={};return m.forEach(f=>{const N=f.field,L=f.values;if(N==="audience")return;const u=L&&L.length>0&&L[0]!==null&&L[0]!==void 0,V=N==="source"&&L?.[0]==="";if(u&&(L[0]!==""||V)){const fe=String(L[0]);n[N]=fe}}),n},[m]),h=l.useCallback((n,f)=>{x(N=>N.find(u=>u.field===n)?N.map(u=>u.field===n?{...u,values:[f]}:u):[...N,ft(n,"is",[f])]),I()},[x,I]),E=l.useCallback(n=>h("location",n),[h]),M=l.useCallback(n=>h("source",n),[h]),F=l.useMemo(()=>{const n={site_uuid:a?.id||"",date_from:$(S),date_to:$(v),timezone:k,member_status:pe(p),post_uuid:"",...j};return!b&&c?.uuid?{...n,post_uuid:c.uuid}:n},[b,c,a?.id,S,v,k,p,j]),{data:y,loading:R}=K({endpoint:"api_kpis",statsConfig:a||{id:""},params:F}),{data:D,loading:U}=K({endpoint:"api_top_locations",statsConfig:a||{id:""},params:F}),{data:A,loading:B}=K({endpoint:"api_top_sources",statsConfig:a||{id:""},params:F}),P=l.useMemo(()=>D?.reduce((n,f)=>n+Number(f.visits),0)||0,[D]),_=l.useMemo(()=>A?A.reduce((n,f)=>n+Number(f.visits||0),0):0,[A]),Q=r?.url,w=r?.icon,Y=l.useMemo(()=>{const n=D?.map(u=>({location:String(u.location),visits:Number(u.visits),percentage:P>0?Number(u.visits)/P:0,isUnknown:le.includes(String(u.location))}))||[],f=n.filter(u=>!u.isUnknown),N=n.filter(u=>u.isUnknown),L=N.length>0?[{location:"Unknown",visits:N.reduce((u,V)=>u+V.visits,0),percentage:N.reduce((u,V)=>u+V.percentage,0)}]:[];return[...f,...L]},[D,P]),g=i||b||R||U||B,C=We(y),T=m.length>0;return t.jsxs(t.Fragment,{children:[t.jsxs(pt,{currentTab:"Web",children:[T&&t.jsx(ee,{children:t.jsx(se,{})}),t.jsxs(ee,{className:`${T?"!mt-0 [grid-area:subactions] lg:!mt-[25px]":"[grid-area:actions]"}`,children:[t.jsx(kt,{filters:m,onChange:x}),!T&&t.jsx(se,{})]})]}),t.jsx(ht,{children:g?t.jsx(ue,{className:"size-full",variant:"plain",children:t.jsx(de,{className:"size-full items-center justify-center",children:t.jsx(Pe,{})})}):y&&y.length!==0&&C.visits!=="0"?t.jsxs(t.Fragment,{children:[t.jsx(Qe,{data:y,range:d}),t.jsxs("div",{className:"flex flex-col gap-6 lg:grid lg:grid-cols-2",children:[t.jsx(yt,{data:Y,isLoading:U,onLocationClick:E}),t.jsx(Ye,{data:A,range:d,siteIcon:w,siteUrl:Q,totalVisitors:_,onSourceClick:M})]})]}):t.jsx("div",{className:"grow",children:t.jsx(Nt,{className:"h-full",description:"Try adjusting filters to see more data.",title:`No visitors ${qe(o)}`,children:t.jsx(J,{strokeWidth:1.5})})})})]})};export{xs as default};
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<link rel="stylesheet" href="./assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
|
|
5
|
-
<link rel="stylesheet" href="./assets/ghost-
|
|
6
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%226.
|
|
5
|
+
<link rel="stylesheet" href="./assets/ghost-3c48d37b32f4fcd7eb15cfd739905a03.css">
|
|
6
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%226.18%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%22e3b2a7ff32%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%227891cacd1a%22%2C%22activitypubFilename%22%3A%22activitypub.js%22%2C%22activitypubHash%22%3A%224d76e4db3f%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22171bc5b3ae%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%226f1c278b75%22%2C%22activitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%22%7D">
|
|
7
7
|
|
|
8
8
|
<meta charset="UTF-8" />
|
|
9
9
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<meta name="apple-mobile-web-app-title" content="Ghost" />
|
|
18
18
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
19
19
|
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
|
20
|
-
<script type="module" crossorigin src="./assets/index-
|
|
20
|
+
<script type="module" crossorigin src="./assets/index-CMTzNTew.js"></script>
|
|
21
21
|
<link rel="stylesheet" crossorigin href="./assets/index-BBrewxpF.css">
|
|
22
22
|
</head>
|
|
23
23
|
<body class="react-admin">
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
<div id="ember-liquid-wormhole"></div>
|
|
39
39
|
<script src="./assets/vendor-0e6161f8da2ad46cf62cef0a53cfb16a.js"></script>
|
|
40
40
|
<script src="./assets/chunk.397.fc6a300ea45f7b5ebab7.js"></script>
|
|
41
|
-
<script src="./assets/chunk.524.
|
|
42
|
-
<script src="./assets/ghost-
|
|
41
|
+
<script src="./assets/chunk.524.9d300778a63b42b0de62.js"></script>
|
|
42
|
+
<script src="./assets/ghost-98a3c5fd6235ebd344594f491cb6d17b.js"></script>
|
|
43
43
|
</body>
|
|
44
44
|
</html>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
|
+
const path = require('path');
|
|
2
3
|
const config = require('../../shared/config');
|
|
3
4
|
const {blogIcon} = require('../../server/lib/image');
|
|
4
5
|
const urlUtils = require('../../shared/url-utils');
|
|
5
6
|
const {SafeString} = require('../services/handlebars');
|
|
7
|
+
const assetHash = require('../services/asset-hash');
|
|
8
|
+
const themeEngine = require('../services/theme-engine');
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Serve either uploaded favicon or default
|
|
@@ -12,54 +15,162 @@ function getFaviconUrl() {
|
|
|
12
15
|
return blogIcon.getIconUrl();
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Get the fallback global asset hash (used for non-theme assets or when file hash unavailable)
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function getGlobalAssetHash() {
|
|
23
|
+
if (!config.get('assetHash')) {
|
|
24
|
+
config.set('assetHash', (crypto.createHash('md5').update(Date.now().toString()).digest('hex')).substring(0, 10));
|
|
25
|
+
}
|
|
26
|
+
return config.get('assetHash');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Safely resolve a path within a root directory, preventing directory traversal
|
|
31
|
+
* @param {string} rootPath - The root directory path
|
|
32
|
+
* @param {string} relativePath - The relative path to resolve
|
|
33
|
+
* @returns {string|null} - The resolved path, or null if it would escape the root
|
|
34
|
+
*/
|
|
35
|
+
function safeResolvePath(rootPath, relativePath) {
|
|
36
|
+
// Normalize and resolve the full path
|
|
37
|
+
const root = path.resolve(rootPath);
|
|
38
|
+
const normalized = relativePath.replace(/^\/+/, ''); // Strip leading slashes
|
|
39
|
+
const fullPath = path.resolve(root, normalized);
|
|
40
|
+
|
|
41
|
+
// Ensure the resolved path is within the root directory
|
|
42
|
+
if (!fullPath.startsWith(root + path.sep) && fullPath !== root) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return fullPath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get SHA256-based hash for a theme asset file
|
|
51
|
+
* @param {string} assetPath - The asset path relative to the theme (e.g., "css/screen.css")
|
|
52
|
+
* @returns {string|null} - Hash string or null if file not found
|
|
53
|
+
*/
|
|
54
|
+
function getThemeAssetHash(assetPath) {
|
|
55
|
+
const activeTheme = themeEngine.getActive();
|
|
56
|
+
if (!activeTheme || !activeTheme.path) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Theme assets are served from {themePath}/assets/{assetPath}
|
|
61
|
+
const assetsRoot = path.join(activeTheme.path, 'assets');
|
|
62
|
+
const fullPath = safeResolvePath(assetsRoot, assetPath);
|
|
63
|
+
if (!fullPath) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return assetHash.getHashForFile(fullPath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get SHA256-based hash for a public asset file
|
|
72
|
+
* Public assets come from two locations:
|
|
73
|
+
* - Static: config.get('paths').publicFilePath (Ghost's built-in files)
|
|
74
|
+
* - Built: config.getContentPath('public') (Generated files like cards.min.css)
|
|
75
|
+
* @param {string} assetPath - The asset path (e.g., "public/ghost.css")
|
|
76
|
+
* @returns {string|null} - Hash string or null if file not found
|
|
77
|
+
*/
|
|
78
|
+
function getPublicAssetHash(assetPath) {
|
|
79
|
+
// Remove 'public/' prefix to get the actual filename
|
|
80
|
+
const filename = assetPath.replace(/^public\//, '');
|
|
81
|
+
|
|
82
|
+
// Try static path first (Ghost's built-in files)
|
|
83
|
+
const staticFilePath = config.get('paths').publicFilePath;
|
|
84
|
+
if (staticFilePath) {
|
|
85
|
+
const staticPath = safeResolvePath(staticFilePath, filename);
|
|
86
|
+
if (staticPath) {
|
|
87
|
+
const hash = assetHash.getHashForFile(staticPath);
|
|
88
|
+
if (hash) {
|
|
89
|
+
return hash;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Try built path (generated files like cards.min.css)
|
|
95
|
+
const builtFilePath = config.getContentPath('public');
|
|
96
|
+
if (builtFilePath) {
|
|
97
|
+
const builtPath = safeResolvePath(builtFilePath, filename);
|
|
98
|
+
if (builtPath) {
|
|
99
|
+
const hash = assetHash.getHashForFile(builtPath);
|
|
100
|
+
if (hash) {
|
|
101
|
+
return hash;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
15
109
|
/**
|
|
16
110
|
* Prepare URL for an asset
|
|
17
|
-
* @param {string|SafeString}
|
|
18
|
-
* @param {boolean} hasMinFile
|
|
111
|
+
* @param {string|SafeString} assetPath - the asset's path
|
|
112
|
+
* @param {boolean} hasMinFile - flag for the existence of a minified version for the asset
|
|
19
113
|
* @returns {string}
|
|
20
114
|
*/
|
|
21
|
-
function getAssetUrl(
|
|
22
|
-
|
|
115
|
+
function getAssetUrl(assetPath, hasMinFile) {
|
|
116
|
+
assetPath = assetPath instanceof SafeString ? assetPath.string : assetPath;
|
|
23
117
|
|
|
24
118
|
// CASE: favicon - this is special path with its own functionality
|
|
25
|
-
if (
|
|
119
|
+
if (assetPath.match(/\/?favicon\.(ico|png)$/)) {
|
|
26
120
|
// @TODO, resolve this - we should only be resolving subdirectory and extension.
|
|
27
121
|
return getFaviconUrl();
|
|
28
122
|
}
|
|
29
123
|
|
|
124
|
+
// Determine asset type
|
|
125
|
+
const isPublicAsset = assetPath.match(/^public\//);
|
|
126
|
+
const isThemeAsset = !isPublicAsset && !assetPath.match(/^asset/);
|
|
127
|
+
|
|
30
128
|
// CASE: Build the output URL
|
|
31
129
|
// Add subdirectory...
|
|
32
130
|
let output = urlUtils.urlJoin(urlUtils.getSubdir(), '/');
|
|
33
131
|
|
|
34
132
|
// Optionally add /assets/
|
|
35
|
-
if (
|
|
133
|
+
if (isThemeAsset) {
|
|
36
134
|
output = urlUtils.urlJoin(output, 'assets/');
|
|
37
135
|
}
|
|
38
136
|
|
|
39
137
|
// replace ".foo" with ".min.foo" if configured
|
|
40
138
|
if (hasMinFile && config.get('useMinFiles') !== false) {
|
|
41
|
-
|
|
139
|
+
assetPath = assetPath.replace(/\.([^.]*)$/, '.min.$1');
|
|
42
140
|
}
|
|
43
141
|
|
|
44
142
|
// Add the path for the requested asset
|
|
45
|
-
output = urlUtils.urlJoin(output,
|
|
143
|
+
output = urlUtils.urlJoin(output, assetPath);
|
|
46
144
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
145
|
+
// Get the appropriate hash for this asset (ignore URL anchor)
|
|
146
|
+
const hashPath = assetPath.includes('#') ? assetPath.slice(0, assetPath.indexOf('#')) : assetPath;
|
|
147
|
+
let hash;
|
|
148
|
+
// Use file-based SHA256 hash if enabled via config (defaults to false for backwards compatibility)
|
|
149
|
+
if (config.get('caching:assets:contentBasedHash:enabled')) {
|
|
150
|
+
if (isThemeAsset) {
|
|
151
|
+
// For theme assets, use file-based SHA256 hash
|
|
152
|
+
hash = getThemeAssetHash(hashPath);
|
|
153
|
+
} else if (isPublicAsset) {
|
|
154
|
+
// For public assets, use file-based SHA256 hash
|
|
155
|
+
hash = getPublicAssetHash(hashPath);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Fallback to global hash if file hash unavailable
|
|
160
|
+
if (!hash) {
|
|
161
|
+
hash = getGlobalAssetHash();
|
|
51
162
|
}
|
|
52
163
|
|
|
53
|
-
// if url has # make sure the hash is at
|
|
164
|
+
// if url has # make sure the hash is at the right place
|
|
54
165
|
let anchor;
|
|
55
|
-
if (
|
|
166
|
+
if (assetPath.match('#')) {
|
|
56
167
|
const index = output.indexOf('#');
|
|
57
168
|
anchor = output.substring(index);
|
|
58
169
|
output = output.slice(0, index);
|
|
59
170
|
}
|
|
60
171
|
|
|
61
172
|
// Finally add the asset hash to the output URL
|
|
62
|
-
output += '?v=' +
|
|
173
|
+
output += '?v=' + hash;
|
|
63
174
|
|
|
64
175
|
if (anchor) {
|
|
65
176
|
output += anchor;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const config = require('../../../shared/config');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Asset Hash Service
|
|
7
|
+
*
|
|
8
|
+
* Provides SHA256-based hashing for theme asset files.
|
|
9
|
+
* Hashes are cached in memory and invalidated when file mtime changes.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Hash length: 16 base64url characters (96 bits) for low collision probability
|
|
13
|
+
// base64url encoding provides more entropy per character than hex (64 vs 16 chars)
|
|
14
|
+
// This is longer than the legacy 10-char global hash, making it easy to distinguish
|
|
15
|
+
const HASH_LENGTH = 16;
|
|
16
|
+
|
|
17
|
+
// Cache structure: { filePath: { hash: string, mtimeMs: number } }
|
|
18
|
+
const hashCache = new Map();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Calculate SHA256 hash of a file's contents
|
|
22
|
+
* @param {string} filePath - Absolute path to the file
|
|
23
|
+
* @returns {string|null} - First 16 characters of SHA256 hash, or null if file doesn't exist
|
|
24
|
+
*/
|
|
25
|
+
function getHashForFile(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
const stat = fs.statSync(filePath);
|
|
28
|
+
const mtimeMs = stat.mtimeMs;
|
|
29
|
+
|
|
30
|
+
// Check cache
|
|
31
|
+
const cached = hashCache.get(filePath);
|
|
32
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
33
|
+
return cached.hash;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Read file and compute hash
|
|
37
|
+
const content = fs.readFileSync(filePath);
|
|
38
|
+
const hash = crypto.createHash('sha256')
|
|
39
|
+
.update(content)
|
|
40
|
+
.digest('base64url')
|
|
41
|
+
.substring(0, HASH_LENGTH);
|
|
42
|
+
|
|
43
|
+
// Clear cache if it exceeds the size limit (safeguard against bugs)
|
|
44
|
+
const maxSize = config.get('caching:assets:contentBasedHash:maxSize');
|
|
45
|
+
if (hashCache.size >= maxSize) {
|
|
46
|
+
hashCache.clear();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Cache the result
|
|
50
|
+
hashCache.set(filePath, {hash, mtimeMs});
|
|
51
|
+
|
|
52
|
+
return hash;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
// File doesn't exist or can't be read
|
|
55
|
+
if (err.code === 'ENOENT' || err.code === 'EACCES') {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clear all cached hashes
|
|
64
|
+
* Should be called when theme is changed/remounted
|
|
65
|
+
*/
|
|
66
|
+
function clearCache() {
|
|
67
|
+
hashCache.clear();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
getHashForFile,
|
|
72
|
+
clearCache
|
|
73
|
+
};
|
|
@@ -21,6 +21,7 @@ const engine = require('./engine');
|
|
|
21
21
|
const themeI18n = require('./i18n');
|
|
22
22
|
const themeI18next = require('./i18next');
|
|
23
23
|
const labs = require('../../../shared/labs');
|
|
24
|
+
const assetHash = require('../asset-hash');
|
|
24
25
|
// Current instance of ActiveTheme
|
|
25
26
|
let currentActiveTheme;
|
|
26
27
|
|
|
@@ -119,9 +120,10 @@ class ActiveTheme {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
mount(siteApp) {
|
|
122
|
-
//
|
|
123
|
-
// @TODO: set this on the theme instead of globally, or use proper file-based hash
|
|
123
|
+
// Reset the global asset hash (used as fallback for non-theme assets)
|
|
124
124
|
config.set('assetHash', null);
|
|
125
|
+
// Clear the file-based asset hash cache (for theme assets)
|
|
126
|
+
assetHash.clearCache();
|
|
125
127
|
// clear the view cache
|
|
126
128
|
siteApp.cache = {};
|
|
127
129
|
// Set the views and engine
|
|
@@ -110,6 +110,12 @@ const forPost = (attrs, frame) => {
|
|
|
110
110
|
_updateTextAttrs(attrs);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// Replace member UUID placeholder for Transistor embeds (URL-encoded {uuid})
|
|
114
|
+
const member = frame.original.context.member;
|
|
115
|
+
if (member && member.uuid && attrs.html) {
|
|
116
|
+
attrs.html = attrs.html.replace(/%7Buuid%7D/gi, member.uuid);
|
|
117
|
+
}
|
|
118
|
+
|
|
113
119
|
if (!Object.prototype.hasOwnProperty.call(frame.options, 'columns') || (frame.options.columns.includes('access'))) {
|
|
114
120
|
attrs.access = memberHasAccess;
|
|
115
121
|
}
|
package/core/server/data/migrations/versions/6.18/2026-02-04-10-42-20-offers-nullable-product-id.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
const {createSetNullableMigration} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
// We need to disable foreign key checks because if MySQL is missing the STRICT_TRANS_TABLES mode, we cannot revert the migration
|
|
4
|
+
module.exports = createSetNullableMigration('offers', 'product_id', {disableForeignKeyChecks: true});
|
|
@@ -483,7 +483,7 @@ module.exports = {
|
|
|
483
483
|
active: {type: 'boolean', nullable: false, defaultTo: true},
|
|
484
484
|
name: {type: 'string', maxlength: 191, nullable: false, unique: true},
|
|
485
485
|
code: {type: 'string', maxlength: 191, nullable: false, unique: true},
|
|
486
|
-
product_id: {type: 'string', maxlength: 24, nullable:
|
|
486
|
+
product_id: {type: 'string', maxlength: 24, nullable: true, references: 'products.id'},
|
|
487
487
|
stripe_coupon_id: {type: 'string', maxlength: 255, nullable: true, unique: true},
|
|
488
488
|
interval: {type: 'string', maxlength: 50, nullable: false, validations: {isIn: [['month', 'year']]}},
|
|
489
489
|
currency: {type: 'string', maxlength: 50, nullable: true},
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
SCHEMA >
|
|
2
|
+
`site_uuid` String,
|
|
3
|
+
`day` Date,
|
|
4
|
+
`post_uuid` String,
|
|
5
|
+
`pathname` String,
|
|
6
|
+
`post_type` LowCardinality(String),
|
|
7
|
+
`member_status` LowCardinality(String),
|
|
8
|
+
`visits` AggregateFunction(uniqExact, String),
|
|
9
|
+
`hits` AggregateFunction(count, UInt64)
|
|
10
|
+
|
|
11
|
+
ENGINE "AggregatingMergeTree"
|
|
12
|
+
ENGINE_PARTITION_KEY "toYYYYMM(day)"
|
|
13
|
+
ENGINE_SORTING_KEY "site_uuid, day, post_type, member_status, post_uuid, pathname"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# api_top_pages_v3 - Optimized top pages endpoint using daily materialized view
|
|
2
|
+
#
|
|
3
|
+
# WHY: The original api_top_pages scans all raw hits and joins with filtered_sessions,
|
|
4
|
+
# which becomes slow (500ms+) and hits memory limits at scale (10M+ rows).
|
|
5
|
+
#
|
|
6
|
+
# HOW: Uses _mv_daily_pages (pre-aggregated by day) for historical data, querying
|
|
7
|
+
# only ~100-200K MV rows instead of millions of raw hits. Today's data is queried
|
|
8
|
+
# fresh from _mv_hits to ensure real-time accuracy.
|
|
9
|
+
#
|
|
10
|
+
# PERFORMANCE (10M rows):
|
|
11
|
+
# - 30-day query: 150ms vs 450ms (3x faster), 25K vs 12M rows scanned
|
|
12
|
+
# - All-time query: 300ms vs MEMORY_LIMIT_EXCEEDED
|
|
13
|
+
#
|
|
14
|
+
# DIFFERENCES: v3 may show slightly lower counts (typically 1-2 per page) for pages
|
|
15
|
+
# where sessions cross midnight at the date range boundary. This is more accurate -
|
|
16
|
+
# v1 incorrectly counts page views outside the requested date range when sessions
|
|
17
|
+
# span multiple days.
|
|
18
|
+
|
|
19
|
+
TOKEN "stats_page" READ
|
|
20
|
+
TOKEN "axis" READ
|
|
21
|
+
|
|
22
|
+
NODE historical_data
|
|
23
|
+
DESCRIPTION >
|
|
24
|
+
Query pre-aggregated daily data for complete days (excluding today).
|
|
25
|
+
Uses uniqExactMerge to combine exact unique session states across days.
|
|
26
|
+
Today is always excluded here and queried fresh from _mv_hits to avoid double-counting.
|
|
27
|
+
|
|
28
|
+
SQL >
|
|
29
|
+
%
|
|
30
|
+
SELECT
|
|
31
|
+
post_uuid,
|
|
32
|
+
pathname,
|
|
33
|
+
uniqExactMerge(visits) as visits
|
|
34
|
+
FROM _mv_daily_pages
|
|
35
|
+
WHERE
|
|
36
|
+
site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
|
|
37
|
+
AND day >= toDate({{ Date(date_from, description="Start date for filtering", required=True) }}, {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})
|
|
38
|
+
AND day <= toDate({{ Date(date_to, description="End date for filtering", required=True) }}, {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})
|
|
39
|
+
AND day != toDate(now(), {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})
|
|
40
|
+
{% if defined(member_status) %}
|
|
41
|
+
AND member_status IN (
|
|
42
|
+
SELECT arrayJoin(
|
|
43
|
+
{{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }}
|
|
44
|
+
|| if('paid' IN {{ Array(member_status) }}, ['comped'], [])
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
{% end %}
|
|
48
|
+
{% if defined(pathname) %} AND pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %}
|
|
49
|
+
{% if defined(post_uuid) %} AND post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %}
|
|
50
|
+
{% if defined(post_type) %}
|
|
51
|
+
{% if post_type == 'post' %}
|
|
52
|
+
AND post_type = 'post'
|
|
53
|
+
{% else %}
|
|
54
|
+
AND (post_type != 'post' OR post_type = '')
|
|
55
|
+
{% end %}
|
|
56
|
+
{% end %}
|
|
57
|
+
GROUP BY post_uuid, pathname
|
|
58
|
+
|
|
59
|
+
NODE today_data
|
|
60
|
+
DESCRIPTION >
|
|
61
|
+
Query raw _mv_hits for today's partial day to ensure freshness.
|
|
62
|
+
|
|
63
|
+
SQL >
|
|
64
|
+
%
|
|
65
|
+
SELECT
|
|
66
|
+
case when post_uuid = 'undefined' then '' else post_uuid end as post_uuid,
|
|
67
|
+
pathname,
|
|
68
|
+
uniqExact(session_id) as visits
|
|
69
|
+
FROM _mv_hits
|
|
70
|
+
WHERE
|
|
71
|
+
site_uuid = {{ String(site_uuid, 'mock_site_uuid', description="Tenant ID", required=True) }}
|
|
72
|
+
AND toDate(timestamp, {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }}) = toDate(now(), {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})
|
|
73
|
+
AND timestamp >= toDateTime({{ Date(date_from, description="Start date for filtering", required=True) }}, {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }})
|
|
74
|
+
AND timestamp < toDateTime({{ Date(date_to, description="End date for filtering", required=True) }}, {{ String(timezone, 'Etc/UTC', description="Site timezone", required=True) }}) + interval 1 day
|
|
75
|
+
{% if defined(member_status) %}
|
|
76
|
+
AND member_status IN (
|
|
77
|
+
SELECT arrayJoin(
|
|
78
|
+
{{ Array(member_status, "'undefined', 'free', 'paid'", description="Member status to filter on", required=False) }}
|
|
79
|
+
|| if('paid' IN {{ Array(member_status) }}, ['comped'], [])
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
{% end %}
|
|
83
|
+
{% if defined(pathname) %} AND pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %}
|
|
84
|
+
{% if defined(post_uuid) %} AND post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %}
|
|
85
|
+
{% if defined(post_type) %}
|
|
86
|
+
{% if post_type == 'post' %}
|
|
87
|
+
AND post_type = 'post'
|
|
88
|
+
{% else %}
|
|
89
|
+
AND (post_type != 'post' OR post_type = '')
|
|
90
|
+
{% end %}
|
|
91
|
+
{% end %}
|
|
92
|
+
GROUP BY post_uuid, pathname
|
|
93
|
+
|
|
94
|
+
NODE combined_results
|
|
95
|
+
DESCRIPTION >
|
|
96
|
+
Combine historical pre-aggregated data with today's fresh data.
|
|
97
|
+
|
|
98
|
+
SQL >
|
|
99
|
+
%
|
|
100
|
+
SELECT
|
|
101
|
+
post_uuid,
|
|
102
|
+
pathname,
|
|
103
|
+
sum(visits) as visits
|
|
104
|
+
FROM (
|
|
105
|
+
SELECT * FROM historical_data
|
|
106
|
+
UNION ALL
|
|
107
|
+
SELECT * FROM today_data
|
|
108
|
+
)
|
|
109
|
+
GROUP BY post_uuid, pathname
|
|
110
|
+
ORDER BY visits DESC
|
|
111
|
+
LIMIT {{ Int32(skip, 0) }}, {{ Int32(limit, 50) }}
|
|
112
|
+
|
|
113
|
+
TYPE ENDPOINT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# mv_daily_pages - Daily aggregation of page visits for api_top_pages_v3
|
|
2
|
+
#
|
|
3
|
+
# Pre-aggregates unique sessions per page per day using uniqExactState,
|
|
4
|
+
# reducing query scans from millions of raw hits to thousands of daily rows.
|
|
5
|
+
|
|
6
|
+
TOKEN "axis" READ
|
|
7
|
+
|
|
8
|
+
NODE mv_daily_pages_0
|
|
9
|
+
SQL >
|
|
10
|
+
SELECT
|
|
11
|
+
site_uuid,
|
|
12
|
+
toDate(timestamp) as day,
|
|
13
|
+
case when post_uuid = 'undefined' then '' else post_uuid end as post_uuid,
|
|
14
|
+
pathname,
|
|
15
|
+
post_type,
|
|
16
|
+
member_status,
|
|
17
|
+
uniqExactState(session_id) as visits,
|
|
18
|
+
countState() as hits
|
|
19
|
+
FROM _mv_hits
|
|
20
|
+
GROUP BY site_uuid, day, post_uuid, pathname, post_type, member_status
|
|
21
|
+
|
|
22
|
+
TYPE materialized
|
|
23
|
+
DATASOURCE _mv_daily_pages
|