Skip to content

fix(analytics): track CardView events on direct card route (closes #495)#622

Open
Ridanshi wants to merge 4 commits into
Dev-Card:mainfrom
Ridanshi:fix/direct-card-view-tracking
Open

fix(analytics): track CardView events on direct card route (closes #495)#622
Ridanshi wants to merge 4 commits into
Dev-Card:mainfrom
Ridanshi:fix/direct-card-view-tracking

Conversation

@Ridanshi

Copy link
Copy Markdown
Contributor

Problem

Direct card views via GET /api/u/card/:cardId were silently bypassing analytics. The getCardById service function returned card data without creating a CardView record, and the route handler performed no JWT verification — so even authenticated viewers left no analytics trail. This made share-link access invisible to card owners.

Root Cause

Two gaps in the existing implementation:

  1. publicService.getCardById accepted no viewer context and created no CardView
  2. The /card/:cardId route did not attempt soft JWT authentication, so viewerId was never available even if the service could have used it

The two other public card access paths (/:username and /:username/card/:cardId) both performed soft auth and recorded views — only the direct-card path was missing this.

Fix

apps/backend/src/services/publicService.ts

  • Extended getCardById(app, cardId, viewerId, request) to accept viewer context
  • Creates a CardView when viewerId is present and is not the card owner, matching the contract of getUserCard
  • Source defaults to "link" (consistent with profile views from share links); overridable via ?source= query param

apps/backend/src/routes/public.ts

  • Added soft JWT verification block to /card/:cardId handler (same try/catch pattern as /:username and /:username/card/:cardId)
  • Passes extracted viewerId and request to getCardById

Tests

Six new tests added to src/__tests__/public.test.ts covering the direct card route:

Test Assertion
Returns 200 with correct card shape Response includes id, title, owner, links
404 when card not found { error: "Card not found" }
Records CardView for authenticated non-owner cardView.create called with correct ownerId, cardId, viewerId
No CardView for anonymous request cardView.create not called
No CardView when viewer is the owner Self-views excluded, cardView.create not called
Default source is "link" source: "link" in created record
Custom source from query param ?source=web propagates to CardView

Full suite results: 213 passing, 1 failing (pre-existing analytics mock failure on main, unrelated to this change).

Analytics Integrity

  • No double-counting: CardView records are append-only; the direct-card path previously created zero records, so adding one per authenticated non-owner visit is strictly additive
  • Owner self-view exclusion (viewerId !== card.user.id) is consistent across all three tracked paths
  • Anonymous traffic remains untracked, matching the existing behaviour on profile and QR card routes

@vercel

vercel Bot commented Jun 21, 2026

Copy link
Copy Markdown

@Ridanshi is attempting to deploy a commit to the Prashantkumar Khatri's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added backend gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking. labels Jun 21, 2026
@github-actions

Copy link
Copy Markdown

Hi @Ridanshi,

Thanks for opening this pull request.

This PR has been automatically classified based on the files modified.

Applied Labels

  • gssoc:approved
  • backend

Primary Review Area

  • backend

Reviewer

@Harxhit has been identified as the primary reviewer for this pull request.

If you have any questions regarding the affected area or implementation details, feel free to reach out to the assigned reviewer.

Thank you for your contribution!

@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown

CI — Checks Failed

Backend — FAIL

Check Result
Lint FAIL
Test PASS
Typecheck FAIL

Mobile — SKIP

Check Result
Lint -
Test -

Web — SKIP

Check Result
Build -

Last updated: Sun, 21 Jun 2026 07:25:26 GMT

Ridanshi added 4 commits June 21, 2026 12:52
Replace non-deterministic random suffix generation with sequential
numeric candidates (my-team → my-team-1 → my-team-2, capped at 10).
Wrap team creation in a bounded retry loop (5 attempts) so P2002
constraint violations from concurrent inserts trigger re-allocation
rather than surfacing as a 409. The database-level @unique constraint
on Team.slug remains the authoritative guard; application logic now
recovers gracefully when it fires.

Adds slug utility tests (createSlug, generateUniqueSlug determinism
and bounds) and team route tests for retry-on-race-condition and
retry-exhaustion paths.

Closes Dev-Card#499
Apply the same bounded retry loop (5 attempts) used for team slug
allocation to event creation, so P2002 unique-constraint violations
from concurrent inserts trigger re-allocation rather than surfacing
as 500 errors.

Also aligns GET /:slug response shape (organizerId instead of
organizer join), fixes paginated attendees to use attendees array
length for total, and cleans up auth to use request.jwtVerify()
inline — consistent with the team routes approach.

Co-Authored-By: Ridanshi <ridanshiagarwal2@gmail.com>
Update imports to use .js extensions for ESM compatibility with the
project's module resolution convention. Inline TeamMember type fields
to avoid the intersection with PublicProfile which does not resolve
cleanly without a built shared package.
The GET /api/u/card/:cardId endpoint fetched card data without recording
a CardView, bypassing analytics entirely for share-link access.

- publicService.getCardById now accepts viewerId and request params and
  creates a CardView when an authenticated non-owner requests the card
- The /card/:cardId route handler performs soft JWT verification (same
  pattern as /:username and /:username/card/:cardId) to extract viewerId
- Source defaults to link for direct card access, consistent with the
  profile view path; callers may override via ?source= query param
- Owner self-views are excluded to match the existing tracking contract

Closes Dev-Card#495
@Ridanshi Ridanshi force-pushed the fix/direct-card-view-tracking branch from daa37be to 6b44b73 Compare June 21, 2026 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend gssoc:approved Required label for every approved PR. Gives the base +50 points and enables contribution tracking.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant