Database: Vercel KV (Redis)
Last Updated: 2026-03-09
Status: Complete Schema Documentation
| # | Table/Collection | Prefix | Purpose | Phase |
|---|---|---|---|---|
| 1 | Users | user: |
User configuration & plans | MVP |
| 2 | Repositories | repo: |
Connected GitHub repos | MVP |
| 3 | Changelog Entries | entry: |
Draft & published entries | MVP |
| 4 | Usage Tracking | usage: |
Monthly usage limits | MVP |
| 5 | Payment Info | user:*:dodo_* |
Payment subscriptions | MVP |
| 6 | Social Posts | social: |
Generated social posts | Phase 2 |
| 7 | Widget Config | widget: |
Widget settings | Phase 2 |
| 8 | Widget Analytics | widget:*: |
Widget impressions/clicks | Phase 2 |
| 9 | Email Subscribers | subscriber: |
Email digest subscribers | Phase 2 |
| 10 | Analytics Views | analytics:views: |
Page view tracking | Phase 2 |
| 11 | Analytics Visitors | analytics:visitor: |
Unique visitor tracking | Phase 2 |
| 12 | Analytics Upvotes | analytics:upvotes: |
Entry upvotes | Phase 2 |
| 13 | Roadmap Items | roadmap: |
GitHub Issues roadmap | Phase 2 |
| 14 | Roadmap Upvotes | roadmap:upvotes: |
Roadmap item upvotes | Phase 2 |
| 15 | GitHub Tokens | user:*:github_token |
GitHub OAuth tokens | MVP |
Purpose: Store user configuration and subscription info
Key Format: user:{userId}
Schema:
interface UserConfig {
id: string; // Clerk user ID
email: string; // User's email
plan: 'free' | 'pro'; // Subscription plan
dodoCustomerId?: string; // DodoPayment customer ID
githubToken?: string; // GitHub OAuth token
createdAt: string; // ISO timestamp
location?: 'in' | 'intl'; // India or International
}Operations:
- ✅ SET on user signup
- ✅ GET on every authenticated request
- ✅ UPDATE on plan change
- ✅ DELETE on account deletion
Used By:
- Dashboard page
- Settings page
- Payment webhook
- All authenticated API routes
Purpose: Store connected GitHub repositories
Key Formats:
repo:{userId}:{repoId}- User's reposrepo:{repoName}:users:{userId}- Repo-to-user mapping
Schema:
interface ConnectedRepo {
id: string; // Unique repo ID
userId: string; // Owner user ID
githubRepoId: number; // GitHub repo ID
name: string; // "owner/repo" format
slug: string; // URL-safe version
isPrivate: boolean; // Public or private
connectedAt: string; // ISO timestamp
webhookId?: number; // GitHub webhook ID
}Operations:
- ✅ SET on repo connect
- ✅ GET to list connected repos
- ✅ DELETE on disconnect
- ✅ SCAN to find repos by user
Used By:
- Settings page
- Onboarding flow
- GitHub sync
- Webhook receiver
Purpose: Store draft and published changelog entries
Key Format: entry:{userId}:{repoId}:{prId}
Schema:
interface ChangelogEntry {
id: string; // Unique entry ID
userId: string; // Owner user ID
repoId: string; // "owner/repo" format
prId: number; // GitHub PR number
title: string; // PR title
body: string; // PR description
category: 'New' | 'Fixed' | 'Improved' | 'Other';
status: 'draft' | 'published';
mergedAt: string; // ISO timestamp
publishedAt?: string; // ISO timestamp (if published)
prUrl: string; // GitHub PR URL
author: string; // PR author
aiRewrite?: string | null; // AI-generated summary
labels: string[]; // GitHub labels
}Operations:
- ✅ SET on PR merge (webhook)
- ✅ GET to list drafts/published
- ✅ UPDATE on edit
- ✅ DELETE on discard
- ✅ SCAN by user and repo
Used By:
- Dashboard page
- Drafts page
- Published page
- Public changelog
- AI rewrite
Purpose: Track monthly usage for plan limits
Key Format: usage:{userId}:{YYYY-MM}
Schema:
interface Usage {
entriesPublished: number; // Count this month
aiRewrites: number; // AI rewrites this month
}Operations:
- ✅ SET on publish/rewrite
- ✅ GET to check limits
- ✅ INCR on each usage
- ✅ RESET on new month
Used By:
- Dashboard (usage card)
- Publish API
- AI rewrite API
- Upgrade prompts
Purpose: Store payment subscription info
Key Formats:
user:{userId}:dodo_customer_id- Customer IDuser:{userId}:dodo_subscription_id- Subscription ID
Schema:
string; // DodoPayment IDOperations:
- ✅ SET on subscription create
- ✅ GET to check subscription
- ✅ DELETE on cancel
- ✅ UPDATE on renewal
Used By:
- Payment webhook
- Settings page
- Upgrade page
- Plan check middleware
Purpose: Store generated social media posts
Key Format: social:{userId}:{entryId}:{platform}
Schema:
interface TwitterDraft {
id: string; // Unique draft ID
entryId: string; // Changelog entry ID
userId: string; // Owner user ID
platform: 'twitter' | 'linkedin';
tweets?: string[]; // Array of tweets (Twitter)
post?: string; // Full post (LinkedIn)
hashtags: string[]; // Hashtags
tone?: string; // Tone used
createdAt: string; // ISO timestamp
}Operations:
- ✅ SET on generate
- ✅ GET to retrieve drafts
- ✅ DELETE on discard
Used By:
- Social posts page
- Generate API
- Preview components
Purpose: Store widget configuration
Key Format: widget:{userId}:{repoId}
Schema:
interface WidgetConfig {
id: string; // Widget ID
userId: string; // Owner user ID
repoId: string; // "owner/repo" format
colors: {
primary: string; // Hex color
background: string; // Hex color
text: string; // Hex color
};
position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
size: 'small' | 'medium' | 'large';
options: {
showDate: boolean;
showCategory: boolean;
showNewBadge: boolean;
};
impressions: number; // Total impressions
clicks: number; // Total clicks
createdAt: Date;
updatedAt: Date;
}Operations:
- ✅ SET on generate/customize
- ✅ GET to retrieve config
- ✅ UPDATE on customize
- ✅ INCR on impression/click
Used By:
- Widget page
- Widget customizer
- Public widget endpoint
- Analytics
Purpose: Track widget performance
Key Formats:
widget:impressions:{widgetId}- Total impressionswidget:clicks:{widgetId}- Total clickswidget:entry:{widgetId}:{entryId}:clicks- Entry clicks
Schema:
number; // CountOperations:
- ✅ INCR on impression
- ✅ INCR on click
- ✅ GET for analytics
Used By:
- Widget analytics page
- Widget track API
- Public widget endpoint
Purpose: Store email digest subscribers
Key Format: subscriber:{repoId}:{email}
Schema:
interface EmailSubscriber {
email: string; // Subscriber email
repoId: string; // "owner/repo" format
subscribedAt: string; // ISO timestamp
confirmed: boolean; // Double opt-in status
confirmToken: string; // Confirmation token
preferences: {
digest: boolean;
majorReleases: boolean;
allUpdates: boolean;
};
}Operations:
- ✅ SET on subscribe
- ✅ GET to check status
- ✅ DELETE on unsubscribe
- ✅ UPDATE on confirm
Used By:
- Subscribe form
- Email confirmation
- Send digest API
- Mailchimp sync
Purpose: Track daily page views
Key Format: analytics:views:{entryId}:{YYYY-MM-DD}
Schema:
interface PageView {
entryId: string; // Entry ID
date: string; // YYYY-MM-DD
views: number; // Total views today
uniqueVisitors: number; // Unique visitors today
visitorIds: string[]; // Anonymous visitor IDs
}Operations:
- ✅ SET on page view
- ✅ GET for analytics
- ✅ INCR on each view
- ✅ SCAN by entry
Used By:
- Analytics track API
- Most viewed page
- Public changelog (tracking)
Purpose: Track unique visitors
Key Format: analytics:visitor:{visitorId}
Schema:
interface Visitor {
id: string; // Visitor ID
fingerprint: string; // Browser fingerprint
firstVisit: string; // ISO date
lastVisit: string; // ISO date
pageViews: number; // Total page views
entries: string[]; // Entries viewed
}Operations:
- ✅ SET on first visit
- ✅ UPDATE on return visit
- ✅ GET for analytics
- ✅ SCAN by entry
Used By:
- Analytics track API
- Visitor stats
Purpose: Track entry upvotes
Key Formats:
analytics:upvotes:{entryId}- Vote countanalytics:upvotes:{entryId}:voters- Voter IDs (set)
Schema:
number // Vote count (for count key)
string[] // Voter IDs (for voters set)Operations:
- ✅ INCR on upvote
- ✅ SADD to add voter
- ✅ SISMEMBER to check if voted
- ✅ GET for count
Used By:
- Upvote button
- Upvote API
- Public changelog
Purpose: Store roadmap items from GitHub Issues
Key Format: roadmap:{userId}:{repoId}:{issueId}
Schema:
interface RoadmapItem {
id: string; // Unique ID
userId: string; // Owner user ID
repoId: string; // "owner/repo" format
issueId: number; // GitHub issue number
title: string; // Issue title
body: string; // Issue description
status: 'planned' | 'in-progress' | 'completed';
upvotes: number; // Upvote count
voterIds: string[]; // Upvoter IDs
githubIssueUrl: string; // GitHub issue URL
labels: string[]; // GitHub labels
linkedEntryId?: string; // Linked changelog entry
createdAt: string; // ISO timestamp
updatedAt: string; // ISO timestamp
}Operations:
- ✅ SET on sync
- ✅ GET to list roadmap
- ✅ UPDATE on status change
- ✅ SCAN by user/repo
Used By:
- Roadmap page
- Public roadmap
- GitHub sync API
- Auto-move to changelog
Purpose: Track roadmap item upvotes
Key Formats:
roadmap:upvotes:{roadmapId}- Vote countroadmap:upvotes:{roadmapId}:voters- Voter IDs (set)
Schema:
number // Vote count
string[] // Voter IDsOperations:
- ✅ INCR on upvote
- ✅ SADD to add voter
- ✅ SISMEMBER to check if voted
Used By:
- Roadmap upvote API
- Public roadmap
Purpose: Store GitHub OAuth tokens
Key Format: user:{userId}:github_token
Schema:
string; // GitHub OAuth tokenOperations:
- ✅ SET on OAuth connect
- ✅ GET for GitHub API calls
- ✅ DELETE on disconnect
Used By:
- GitHub sync
- Repo connect
- Issues sync
1. User signs in with GitHub
↓
2. Clerk creates user account
↓
3. Create user record in KV
→ SET user:{userId} = {
id, email, plan: 'free',
createdAt, location
}
↓
4. Redirect to onboarding
↓
5. Connect repository
→ SET repo:{userId}:{repoId}
→ SET user:{userId}:github_token
↓
6. Dashboard ready
1. PR merged on GitHub
↓
2. GitHub sends webhook
→ POST /api/github/sync
↓
3. Verify webhook signature
↓
4. Extract PR data
↓
5. Create draft entry
→ SET entry:{userId}:{repoId}:{prId} = {
title, body, category,
status: 'draft', mergedAt, ...
}
↓
6. Draft appears in dashboard
1. User clicks "Publish" on draft
↓
2. Check plan limits
→ GET usage:{userId}:{YYYY-MM}
↓
3. If under limit:
→ UPDATE entry:{id} = { status: 'published', publishedAt }
→ INCR usage:{userId}:{YYYY-MM}.entriesPublished
↓
4. If Phase 2 enabled:
→ Check for email subscribers
→ GET subscriber:{repoId}:*
→ Send digest email (if any)
↓
5. Entry live on public changelog
1. User generates widget
↓
2. SET widget:{userId}:{repoId} = { config }
↓
3. User embeds script on website
↓
4. Visitor loads page
↓
5. Widget script loads
→ GET /api/widget/{widgetId}
↓
6. Track impression
→ INCR widget:impressions:{widgetId}
↓
7. Display changelog entries
1. User visits public changelog
↓
2. Clicks "Subscribe to updates"
↓
3. Enters email
↓
4. SET subscriber:{repoId}:{email} = {
email, repoId, subscribedAt,
confirmed: false, confirmToken
}
↓
5. Send confirmation email
↓
6. User clicks confirmation link
↓
7. UPDATE subscriber = { confirmed: true }
↓
8. Subscribed to digest
- ✅ Users (
user:) - ✅ Repositories (
repo:) - ✅ Changelog Entries (
entry:) - ✅ Usage Tracking (
usage:) - ✅ Payment Info (
user:*:dodo_*)
- ✅ Social Posts (
social:) - ✅ Widget Config (
widget:) - ✅ Widget Analytics (
widget:*:) - ✅ Email Subscribers (
subscriber:) - ✅ Analytics Views (
analytics:views:) - ✅ Analytics Visitors (
analytics:visitor:) - ✅ Analytics Upvotes (
analytics:upvotes:) - ✅ Roadmap Items (
roadmap:) - ✅ Roadmap Upvotes (
roadmap:upvotes:) - ✅ GitHub Tokens (
user:*:github_token)
- ✅ GitHub tokens encrypted at rest
- ✅ No sensitive data in logs
- ✅ Clerk handles auth securely
- ✅ Anonymous visitor IDs (no PII)
- ✅ Email double opt-in
- ✅ Unsubscribe functionality
- ✅ GDPR-compliant analytics
- ✅ User ID verification on all requests
- ✅ Repo ownership verification
- ✅ Webhook signature verification
- ✅ Rate limiting per user
- Key Naming: Consistent prefixes for easy scanning
- Batch Operations: Promise.all for parallel fetches
- Caching: Client-side where appropriate
- Indexing: Maintain reverse mappings for lookups
- Cleanup: Delete orphaned records
- Free user: ~100-500 KB
- Pro user: ~1-5 MB
- Average: ~500 KB per user
All data properly stored and connected! ✅
Last Updated: 2026-03-09
Status: Complete Database Schema Documentation