Skip to content

Added gift dimension to the web analytics data files#28854

Open
jonatansberg wants to merge 5 commits into
mainfrom
ber-3728-gift-links-analytics-datafiles
Open

Added gift dimension to the web analytics data files#28854
jonatansberg wants to merge 5 commits into
mainfrom
ber-3728-gift-links-analytics-datafiles

Conversation

@jonatansberg

@jonatansberg jonatansberg commented Jun 24, 2026

Copy link
Copy Markdown
Member

ref https://linear.app/ghost/issue/BER-3746/integrate-gift-link-usage-tracking-with-analytics

The Tinybird data-files slice of moving gift-link usage tracking onto the existing web-analytics pipeline. Independent of the producer/proxy PRs — can be reviewed and merged on its own (off main).

What this adds

1. gift_link column on _mv_hits — extracted from the page_hit payload (JSONExtractString(payload, 'gift_link')), a plain String like post_uuid. Added at the same position in both mv_hits.pipe's materialised output and the _mv_hits.datasource schema so the MV column mapping stays aligned. No existing consumer does SELECT * from _mv_hits, so the new column is non-breaking.

2. A gift_link segment (include / exclude) — a tri-state toggle mirroring the existing post_uuid filter plumbing: omit gift_link for all traffic, pass gift_link=true (or any truthy value) for gift-only, or gift_link=false/0 to exclude gift traffic. Renders as {% if defined(gift_link) %}{% if gift_link == 'false' or gift_link == '0' %} and gift_link = '' {% else %} and gift_link != '' {% end %}{% end %} (the post_type pipes use the same param-value-conditional pattern). Threaded through filtered_sessions_v2 (the shared chokepoint, so session-joined endpoints inherit it) plus the v2 endpoints that filter _mv_hits directly: api_kpis_v2, api_top_pages_v3, api_top_locations_v2, api_active_visitors_v2. Lets operators segment "gift traffic" in the normal reports (Goal: segmentable). In api_kpis_v2, gift-only queries are routed through the hit-level pathname_pageviews node so gift pageviews count gift-link hits, not every pageview in a session that merely used a gift link.

3. gift_link on _mv_daily_pagesapi_top_pages_v3 is the one segmented endpoint that doesn't read _mv_hits for historical days; it reads the daily rollup _mv_daily_pages, which had no gift_link column, so the segment would have errored (UNKNOWN_IDENTIFIER) on any gift-filtered top-pages query. gift_link is added as a dimension to the rollup (mv_daily_pages.pipe GROUP BY + the datasource schema/sorting key). Non-gift queries are unchanged — they re-aggregate by (post_uuid, pathname) with uniqExactMerge, which collapses the gift split back to the same totals; gift queries select gift_link != ''.

4. api_gift_link_visits endpoint — groups by gift_link, scoped by site_uuid (+ optional post_uuid / date range), returning visits = uniqExact(session_id), views = count(), last_seen = max(timestamp). Defaults to all-time, since a link's usage is its lifetime total. Feeds the per-link usage view (Goal: per-link tracking). Registered in the TINYBIRD_PIPES allowlist (tinybird-service.js) so JWT-authed Ghost calls to it aren't denied.

Dependencies & sequencing

  • Producer (Added gift dimension to the web analytics tracker on gift reads #28853) and proxy (TryGhost/TrafficAnalytics) send/forward gift_link. Until the proxy lands, gift_link is dropped before Tinybird, so the column is empty and the segment matches nothing — safe no-op.
  • Re-materialising _mv_hits and _mv_daily_pages to backfill gift_link for already-ingested rows is a follow-up populate (the raw payload retains it). Feature is unreleased, so no live audience.

Not included

Tests

tb test run (the Tinybird endpoint tests). A few fixtures/analytics_events.ndjson rows are tagged with a gift_link token (so unfiltered counts are unchanged), and each modified endpoint gets a gift_link-filter case — api_kpis_v2, api_top_pages_v3, api_top_locations_v2, api_active_visitors_v2 — plus a new tests/api_gift_link_visits.yaml. The kpis case pins the gift-only fix: gift pageviews count gift-link hits (2), not every pageview in a session that used a gift link (3). All 29 test files pass.

v1 endpoints are left untouched (segment targets the v2 surface).

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

gift_link is added to the Tinybird hit and daily page data model. The mv_hits pipe extracts and propagates gift_link, mv_daily_pages groups daily aggregates by gift_link, and both datasource definitions include gift_link. Existing queries add optional gift_link-based filters, a new gift_link_visits endpoint aggregates visits, views, and last_seen by gift_link, and Tinybird JWT scope generation includes that endpoint.

Suggested reviewers

  • 9larsons
  • EvanHahn
  • troyciesco
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change: adding a gift analytics dimension to web analytics data files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description clearly matches the changeset by explaining the new gift_link analytics support, schema updates, endpoints, and tests.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ber-3728-gift-links-analytics-datafiles

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nx-cloud

nx-cloud Bot commented Jun 24, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 4d0e07e

Command Status Duration Result
nx run ghost:test:ci:integration ✅ Succeeded 2m 41s View ↗
nx run ghost:test:integration ✅ Succeeded 2m 44s View ↗
nx run ghost:test:legacy ✅ Succeeded 2m 51s View ↗
nx run ghost:test:e2e ✅ Succeeded 2m 25s View ↗
nx run-many --target=build --projects=tag:publi... ✅ Succeeded <1s View ↗
nx run-many -t test:unit -p ghost ✅ Succeeded 30s View ↗
nx run @tryghost/admin:build ✅ Succeeded 10s View ↗
nx run-many -t lint -p ghost ✅ Succeeded 31s View ↗
Additional runs (2) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-06-30 23:50:50 UTC

@jonatansberg jonatansberg force-pushed the ber-3728-gift-links-analytics-datafiles branch from b44b819 to c890002 Compare June 24, 2026 10:48
@jonatansberg jonatansberg changed the title Added gift_id dimension to the web analytics data files Added gift dimension to the web analytics data files Jun 24, 2026
@jonatansberg jonatansberg force-pushed the ber-3728-gift-links-analytics-datafiles branch 2 times, most recently from 7a8290d to 84de5a6 Compare June 24, 2026 14:45
@jonatansberg jonatansberg marked this pull request as ready for review June 25, 2026 14:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84de5a6d2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

TOKEN "stats_page" READ
TOKEN "axis" READ

NODE gift_link_visits

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the new pipe to JWT scopes

This new endpoint is not included in TINYBIRD_PIPES in ghost/core/core/server/services/tinybird/tinybird-service.js (lines 47-72), and _generateToken only grants PIPES:READ scopes for that list. On sites using workspaceId/adminToken JWT auth, any Ghost server/UI call to api_gift_link_visits will be rejected even though the Tinybird datafile exists; add the pipe to the generated scope list before wiring consumers to it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is valid and will need to be addressed at some point, or else Ghost Admin won't be able to access api_gift_link_visits.pipe. Doesn't need to be in this PR, but it will be required eventually.

{% if defined(location) %} and location = {{ String(location, description="Location to filter on", required=False) }} {% end %}
{% if defined(pathname) %} and pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %}
{% if defined(post_uuid) %} and post_uuid = {{String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %}
{% if defined(gift) %} and gift != '' {% end %}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use gift-filtered pageviews for gift-only KPI queries

When api_kpis_v2 is called with only the new gift segment, this hit-level filter is calculated in pathname_pageviews, but finished_data only joins/uses that node when pathname or post_uuid is defined (lines 164 and 169). As a result, gift-only KPI responses fall back to b.pageviews from session-level data and count every pageview in sessions that had a gift hit, so a visitor who lands via a gift link and then browses other pages inflates gift pageviews; include defined(gift) in the condition that uses pathname_pageviews.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is valid — adding the endpoint tests should help confirm that.

Basically with this current implementation, this will return the total count of pageviews for any session that used a gift link, rather than the count of pageviews using gift links.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/server/data/tinybird/endpoints/api_gift_link_visits.pipe`:
- Around line 1-32: The new Tinybird pipe for gift link visits is missing from
the JWT scope allowlist, which will cause authenticated requests to fail. Update
the `TINYBIRD_PIPES` allowlist in `tinybird-service.js` to include
`api_gift_link_visits`, making sure the new `gift_link_visits` endpoint is
covered alongside the other pipe names used for JWT authorization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a5a82431-18c2-4aba-a797-86c7113df75d

📥 Commits

Reviewing files that changed from the base of the PR and between 0db0aad and 84de5a6.

📒 Files selected for processing (10)
  • ghost/core/core/server/data/tinybird/datasources/_mv_daily_pages.datasource
  • ghost/core/core/server/data/tinybird/datasources/_mv_hits.datasource
  • ghost/core/core/server/data/tinybird/endpoints/api_active_visitors_v2.pipe
  • ghost/core/core/server/data/tinybird/endpoints/api_gift_link_visits.pipe
  • ghost/core/core/server/data/tinybird/endpoints/api_kpis_v2.pipe
  • ghost/core/core/server/data/tinybird/endpoints/api_top_locations_v2.pipe
  • ghost/core/core/server/data/tinybird/endpoints/api_top_pages_v3.pipe
  • ghost/core/core/server/data/tinybird/pipes/filtered_sessions_v2.pipe
  • ghost/core/core/server/data/tinybird/pipes/mv_daily_pages.pipe
  • ghost/core/core/server/data/tinybird/pipes/mv_hits.pipe

@EvanHahn EvanHahn removed request for 9larsons and EvanHahn June 26, 2026 15:52
@jonatansberg jonatansberg force-pushed the ber-3728-gift-links-analytics-datafiles branch from 84de5a6 to 79d0831 Compare June 29, 2026 09:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79d0831de3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

JSONExtractString(payload, 'member_status') as member_status,
JSONExtractString(payload, 'post_uuid') as post_uuid,
JSONExtractString(payload, 'post_type') as post_type,
JSONExtractString(payload, 'gift_link') as gift_link,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Populate gift_link before filtering on it

When these datafiles are deployed with the current Ghost tracker, this column is never populated: ghost_head only emits tb_site_uuid, tb_post_uuid, tb_post_type, tb_member_uuid, and tb_member_status, and the client only merges those tb_ attributes into page_hit payloads; I also found no other repo-wide writer for gift_link. As a result, every new gift_link != '' filter and api_gift_link_visits will return no rows for real gift-link traffic unless the gift-link token is added to the tracked payload before extracting it here.

Useful? React with 👍 / 👎.

@cmraible cmraible left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add/update the Tinybird endpoint tests in ghost/core/core/server/data/tinybird/tests/*?

You'll need to update ghost/core/core/server/data/tinybird/fixtures/analytics_events.ndjson to add or update some rows to include a gift_link so there's some data to work with, then I'd like to see at least one test that includes the gift_link parameter as a filter for each modified endpoint + create host/core/core/server/data/tinybird/tests/api_gift_link_visits.yaml for the new endpoint.

The code looks okay to me, but it's really hard to review and be sure without the test coverage.

@jonatansberg jonatansberg force-pushed the ber-3728-gift-links-analytics-datafiles branch 2 times, most recently from 33611d2 to 5acdbc9 Compare June 30, 2026 06:15

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5acdbc96b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

{% if defined(location) %} and location = {{ String(location, description="Location to filter on", required=False) }} {% end %}
{% if defined(pathname) %} and pathname = {{ String(pathname, description="Pathname to filter on", required=False) }} {% end %}
{% if defined(post_uuid) %} and post_uuid = {{ String(post_uuid, description="Post UUID to filter on", required=False) }} {% end %}
{% if defined(gift_link) %} and gift_link != '' {% end %}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add hit-level gift filtering to top pages

When this new gift_link predicate is inherited by api_top_pages_v2 in a tinybird:stats:version = v2 deployment, it only narrows filtered_sessions_v2 to sessions that had at least one gift hit; api_top_pages_v2.pipe then joins those sessions back to _mv_hits and groups all hits without an h.gift_link != '' predicate. A visitor who enters through a gift link and then opens another page will make that non-gift page appear in gift-filtered top content, so mirror the hit-level filter used in api_top_pages_v3/api_top_locations_v2 before exposing this segment.

Useful? React with 👍 / 👎.

@jonatansberg jonatansberg force-pushed the ber-3728-gift-links-analytics-datafiles branch from 5acdbc9 to 1fbc554 Compare June 30, 2026 07:29

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1fbc554e71

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

'api_top_devices_v2'
'api_top_devices_v2',
// gift-link per-link usage
'api_gift_link_visits'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Provide a versioned gift-link visits endpoint

When tinybird:stats.version is configured (for example v2), the normal Tinybird clients append that suffix to every requested endpoint (apps/admin-x-framework/src/utils/stats-config.ts:18 and ghost/core/core/server/services/stats/utils/tinybird.js:39-42), so a UI/server call for api_gift_link_visits becomes api_gift_link_visits_v2. This change only creates/scopes the unsuffixed pipe, so versioned stats deployments will hit a missing/unscoped endpoint unless the new pipe gets a versioned alias or callers explicitly bypass versioning.

Useful? React with 👍 / 👎.

ref https://linear.app/ghost/issue/BER-3746/integrate-gift-link-usage-tracking-with-analytics

- gift-link usage is moving onto the existing analytics pipeline; this is the Tinybird side
- _mv_hits gains a `gift_link` column (the gift-link token, extracted from the page_hit payload, kept in step with the materialised output ordering)
- a `gift_link` segment is threaded through filtered_sessions_v2 and the v2 endpoints that filter _mv_hits directly, mirroring the existing post_uuid plumbing: omit it for all traffic, `gift_link=true` (or any truthy value) for gift-only, `gift_link=false`/`0` to exclude gift traffic
- in api_kpis_v2, route gift-segmented queries through the hit-level pathname_pageviews node (defined(gift_link)) so gift pageviews count gift-link hits, not every pageview in a session that used a gift link
- _mv_daily_pages (the daily rollup behind api_top_pages_v3) also gains `gift_link` as a dimension, so the segment works on the top-pages historical path instead of erroring on a missing column; non-gift queries merge across the gift_link split via uniqExactMerge and are unchanged
- new api_gift_link_visits endpoint groups by gift_link for per-link visits / views / last_seen, registered in the tinybird-service JWT scope allowlist (TINYBIRD_PIPES) so authenticated Ghost calls to it are permitted
- depends on the proxy passing gift_link through to Tinybird (separate PR); until then the column is empty and the segment matches nothing

- endpoint tests: tag gift-link rows in fixtures/analytics_events.ndjson and add include (`gift_link=true`) and exclude (`gift_link=false`) cases to api_kpis_v2 / api_top_pages_v3 / api_top_locations_v2 / api_active_visitors_v2, plus a new tests/api_gift_link_visits.yaml. The kpis include case asserts gift pageviews count gift-link hits (2), not every pageview in a session that used a gift link (3)
@jonatansberg jonatansberg force-pushed the ber-3728-gift-links-analytics-datafiles branch from 1fbc554 to 188f09b Compare June 30, 2026 09:46
luissazevedo added a commit that referenced this pull request Jun 30, 2026
ref https://linear.app/ghost/issue/BER-3746/integrate-gift-link-usage-tracking-with-analytics

- #28854 now resolves the gift_link param by value: 'false'/'0' selects traffic with no gift link ("not used"), any other value selects gift-link traffic ("used")
- updates the filter option values from used/not_used to true/false so "not used" sends gift_link=false and returns the correct data instead of being treated as "used"
- labels are unchanged ("used" / "not used"); only the underlying query value changed

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ref https://linear.app/ghost/issue/BER-3746/integrate-gift-link-usage-tracking-with-analytics

- web analytics always queries the version-suffixed endpoint (getStatEndpointUrl
  appends stats.version, and every analytics-enabled deployment runs v2), so the
  unversioned api_gift_link_visits pipe was never requested in any environment
- renamed the pipe and its test to api_gift_link_visits_v2 and scoped only the v2
  name, instead of shipping a base+v2 pair where the base is dead weight
- the frontend hook still names api_gift_link_visits; the framework's version
  suffix resolves it to the v2 pipe

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a280d251a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +73 to +75
// gift-link per-link usage (v2 only; web analytics always queries the
// version-suffixed endpoint, so the base name is never requested)
'api_gift_link_visits_v2'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the unversioned gift-link pipe for unversioned stats

When tinybird:stats.version is unset, the browser helper leaves the endpoint name unsuffixed (apps/admin-x-framework/src/utils/stats-config.ts:17-18) and the server Tinybird client does the same (ghost/core/core/server/services/stats/utils/tinybird.js:39-42). This change only creates/scopes api_gift_link_visits_v2, so default/local stats configs without a version will request api_gift_link_visits.json and either 404 or be denied by the JWT scope once the per-link usage view is wired up.

Useful? React with 👍 / 👎.

ref https://linear.app/ghost/issue/BER-3746/integrate-gift-link-usage-tracking-with-analytics

- api_top_pages / api_top_pages_v2 applied gift_link only via the filtered_sessions
  join, which selects whole sessions that had any gift hit and then counts all
  their pageviews — so the top content list was session-scoped and surfaced a gift
  session's non-gift pages (e.g. the homepage) under a gift-link filter
- gift_link is a per-hit attribute like pathname, so apply it on the pipe's own hit
  scan (mirroring the pathname filter and api_top_pages_v3 / api_top_locations_v2),
  making views/visitors hit-scoped and consistent with the KPIs and every other
  gift-aware pipe
- added gift_link include/exclude test cases proving a gift session's non-gift
  pageviews no longer appear; full datafiles suite passes 29/29

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd340367ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

'api_top_devices_v2',
// gift-link per-link usage (v2 only; web analytics always queries the
// version-suffixed endpoint, so the base name is never requested)
'api_gift_link_visits_v2'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep gift-link usage off the shared stats token

When JWT Tinybird auth is enabled, adding this pipe to TINYBIRD_PIPES grants it to the same /tinybird/token response that only requires members.browse, while the normal gift-link API checks manage.gift_link(id) and edit.post(id) before exposing tokens. Because api_gift_link_visits_v2 returns raw gift_link values for the whole site with only the fixed site_uuid, any caller or integration that can obtain the stats token can enumerate gift-link tokens for posts it is not allowed to edit; keep this pipe out of the shared stats JWT or proxy it through a permission-checked endpoint.

Useful? React with 👍 / 👎.

cmraible added 2 commits June 30, 2026 19:24
ref https://linear.app/tryghost/issue/BER-3728/

Ghost uses the unsuffixed Tinybird stats endpoints by default, so gift-link analytics needs to be implemented on those live pipes as well as the experimental suffixed variants. The raw per-link usage pipe is intentionally left out of the shared stats JWT because it returns gift-link tokens and needs permission-checked access.
ref https://linear.app/tryghost/issue/BER-3728/

Gift-link analytics endpoints need JWT access in both unversioned and v2 Tinybird stats deployments.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants