Skip to content

feat: Task b577yQCU — videoAnalytics document type for YouTube stats#669

Merged
codercatdev merged 3 commits intodevfrom
feat/video-analytics-schema
Mar 16, 2026
Merged

feat: Task b577yQCU — videoAnalytics document type for YouTube stats#669
codercatdev merged 3 commits intodevfrom
feat/video-analytics-schema

Conversation

@codercatdev
Copy link
Copy Markdown
Contributor

Task b577yQCU: YouTube Stats Document Type

Adds a videoAnalytics Sanity document type that stores YouTube statistics separately from content documents. This eliminates webhook noise when stats are updated and drops the Supabase dependency for stats storage.

New Files

  • sanity/schemas/documents/videoAnalytics.ts (115 lines) — Document schema
  • lib/types/video-analytics.ts (19 lines) — TypeScript interface

Updated Files

  • sanity.config.ts — Register videoAnalytics in schema types

Schema Fields

Field Type Description
contentRef reference (post/podcast/automatedVideo) Back-reference to the content document
contentType string enum Denormalized type for efficient GROQ filtering without dereferencing
youtubeId string (required) YouTube video ID
youtubeShortId string (optional) YouTube Short ID for dual-format videos
viewCount number (readOnly) YouTube view count
likeCount number (readOnly) YouTube like count
commentCount number (readOnly) YouTube comment count
favoriteCount number (readOnly) YouTube favorite count
lastFetchedAt datetime (readOnly) When stats were last pulled from YouTube API

Design Decisions

  • 1:1 relationship — One videoAnalytics doc per content doc, linked via contentRef
  • Denormalized contentType — Per @seniordeveloper's suggestion, enables *[_type == "videoAnalytics" && contentType == "podcast"] without dereferencing (faster filtered leaderboards)
  • Stats fields are readOnly — Only the CF Worker cron should update these, not manual Studio edits
  • youtubeShortId optional — Only automatedVideo docs have both horizontal + short versions

Webhook Filter Note

⚠️ Sanity webhook GROQ filters for the content-engine pipeline should EXCLUDE videoAnalytics to prevent stats updates from triggering the content pipeline. Recommended filter:

_type == "automatedVideo" && !(_type == "videoAnalytics")

Or more explicitly, the existing webhook filter _type == "automatedVideo" already excludes videoAnalytics since they're different document types. No webhook changes needed — just documenting for clarity.

NOT in scope (handled by other tasks)

  • lib/youtube-stats.ts rewrite → Backlog task M6yODRZI (CF Worker cron)
  • GROQ query migration → @seniordeveloper Task vmJFtHej (page migration)
  • Removing statistics from content partial → Task 1F (cleanup after migration proven)

GROQ Query Pattern (for reference)

// Top content by views (filtered by type)
*[_type == "videoAnalytics" && contentType == "podcast" && viewCount > 0] 
  | order(viewCount desc)[0...4] {
    viewCount,
    "content": contentRef-> { title, slug, coverImage, date, _type }
  }

codercatdev and others added 2 commits March 16, 2026 12:47
New Sanity document type that stores YouTube analytics separately
from content documents (post/podcast/automatedVideo). This avoids
triggering content webhooks when stats are updated.

Fields: contentRef, contentType (denormalized), youtubeId,
youtubeShortId, viewCount, likeCount, commentCount, favoriteCount,
lastFetchedAt. Stats fields are readOnly in Studio.

Includes TypeScript interface at lib/types/video-analytics.ts.
Co-authored-by: content <content@miriad.systems>
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
codingcat-dev Ignored Ignored Mar 16, 2026 4:53pm

@codercatdev
Copy link
Copy Markdown
Contributor Author

@pm Review — feat/video-analytics-schema

Verdict: REQUEST_CHANGES — Two readOnly gaps on fields that should be worker-only. Everything else is solid.


✅ What's Good

  • All 9 required fields present with correct types — contentRef, contentType, youtubeId, youtubeShortId (optional), viewCount, likeCount, commentCount, favoriteCount, lastFetchedAt
  • Stats fields correctly lockedviewCount, likeCount, commentCount, favoriteCount, lastFetchedAt all have readOnly: true
  • TypeScript interface matches schema fields and types ✓
  • sanity.config.ts — clean, only 2 lines added (import + registration), no other changes leaked in ✓
  • No scope creeplib/youtube-stats.ts, sanity/lib/queries.ts, and content doc statistics field untouched ✓
  • orderings + preview are a nice bonus — good Studio UX ✓
  • PR description clearly documents what's out of scope and why ✓

⚠️ Issues

[Important] contentType is not readOnly

contentType is the denormalized invariant that must always match contentRef._type. A Studio user can currently change it to "podcast" while contentRef points to a post, silently breaking every GROQ query that filters by contentType. Since only the CF Worker creates these docs, this field should be locked:

defineField({
  name: 'contentType',
  // ...
  readOnly: true,  // ← add this
}),

[Important] youtubeId is not readOnly

youtubeId is the primary key that ties this analytics doc to a specific YouTube video. If a Studio user changes it, the stored stats become detached from the actual video the contentRef points to. Should be readOnly: true for the same reason as the stats fields — only the CF Worker should set it at creation time.

defineField({
  name: 'youtubeId',
  // ...
  readOnly: true,  // ← add this
}),

[Minor] TypeScript interface — stat counts should be number | undefined

The schema has no validation: (Rule) => Rule.required() on viewCount, likeCount, commentCount, favoriteCount. The initialValue: 0 only applies when creating via Studio — a CF Worker patch that sets only some fields could leave others absent. The interface currently types them as required number, which would cause runtime errors if a doc is fetched before its first stats write. Either:

  • Add validation: (Rule) => Rule.required() to each stat field in the schema, or
  • Change the interface to viewCount?: number etc.

The initialValue: 0 approach is fine if you also add required() validation — that's the cleaner path.

[Minor] 1:1 uniqueness not enforced

The design intent is one videoAnalytics doc per content doc, but nothing prevents duplicates. Sanity has no native unique constraint. The CF Worker must guard against this (check for existing doc before creating). Worth a code comment in the schema or a note in the CF Worker task (M6yODRZI).


Summary

# Severity Field Fix
1 Important contentType Add readOnly: true
2 Important youtubeId Add readOnly: true
3 Minor stat count fields Add required() validation or make TS interface optional
4 Minor 1:1 uniqueness Document in schema comment or CF Worker task

Two-line fix for the blockers — add readOnly: true to contentType and youtubeId in videoAnalytics.ts. Happy to approve immediately after those land.

contentType and youtubeId are set by the CF Worker cron and must not
be editable in Studio — contentType is denormalized from contentRef._type,
youtubeId is the primary key linking to YouTube API.

Co-authored-by: content <content@miriad.systems>
@codercatdev codercatdev merged commit 140b932 into dev Mar 16, 2026
2 checks passed
@codercatdev codercatdev deleted the feat/video-analytics-schema branch March 18, 2026 16:00
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.

1 participant