AI-powered organization for your YouTube liked videos
Ballroom helps you automatically organize your YouTube liked videos into categories using AI. Sync your liked videos, let AI categorize them, and keep everything organized in one place.
- π Secure Authentication - Sign in with Google OAuth (Better Auth)
- πΊ YouTube Sync - Automatically sync your liked videos from YouTube
- π€ AI Categorization - Intelligent video categorization using Google Generative AI
- π Category Management - Create, edit, and organize custom categories
- π― Smart Filtering - Filter videos by category with real-time counts
- π Pagination - Efficient browsing of large video collections
- π Background Jobs - Automated syncing and categorization via Trigger.dev
- π± Responsive Design - Beautiful, modern UI built with Radix UI and Tailwind CSS
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js (App Router) | 16.1.0 |
| Language | TypeScript | 5.x |
| React | React | 19.2.0 |
| Styling | Tailwind CSS | 4.x |
| UI Components | Radix UI + shadcn/ui | Latest |
| Database | PostgreSQL | - |
| ORM | Drizzle ORM | 0.44.7 |
| Auth | Better Auth | 1.3.27 |
| AI | Google Generative AI | - |
| Background Jobs | Trigger.dev | 4.3.0 |
| Forms | React Hook Form + Zod | 7.x / 4.x |
| Package Manager | pnpm | - |
Before you begin, ensure you have:
- Node.js 20.x or later
- pnpm installed (
npm install -g pnpm) - PostgreSQL database (local or hosted)
- Google Cloud Project with:
- OAuth 2.0 credentials (Client ID & Secret)
- YouTube Data API v3 enabled
- Google Generative AI API enabled
- Trigger.dev account and project
git clone <repository-url>
cd ballroompnpm installCreate a .env.local file in the root directory:
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/ballroom"
# Google OAuth & YouTube API
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
# Google Generative AI API
GOOGLE_GENERATIVE_AI_API_KEY="your-google-ai-api-key"
# Better Auth
BETTER_AUTH_SECRET="generate-a-random-secret-here"
BETTER_AUTH_URL="http://localhost:3000" # Optional, defaults to current URL
# Trigger.dev
TRIGGER_SECRET_KEY="your-trigger-dev-secret-key"
# Node Environment
NODE_ENV="development"Generate a secure secret for Better Auth:
openssl rand -base64 32Validate environment variables:
pnpm check-envGenerate migrations:
pnpm db:generateRun migrations:
pnpm db:migrateOr push schema directly (development only):
pnpm db:pushOpen Drizzle Studio (database GUI):
pnpm db:studio- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable YouTube Data API v3 and Google Generative AI API
- Go to APIs & Services > Credentials
- Create OAuth 2.0 Client ID (Web application)
- Add authorized redirect URI:
http://localhost:3000/api/auth/callback/google - Copy Client ID and Secret to
.env.local
- Sign up at Trigger.dev
- Create a new project
- Copy your project reference to
trigger.config.ts - Copy your secret key to
.env.local - Deploy your workflows:
npx trigger.dev@latest deploy
pnpm devOpen http://localhost:3000 in your browser.
src/
βββ app/ # Next.js App Router pages & API routes
β βββ (auth)/ # Auth route group (signin page)
β βββ api/ # API routes (REST endpoints)
β β βββ auth/ # Better Auth catch-all route
β β βββ categories/ # Category CRUD endpoints
β β βββ categorize/ # AI categorization endpoint
β β βββ onboarding/ # Onboarding flow
β β βββ sync-status/ # Sync status endpoint
β β βββ youtube/ # YouTube sync & video endpoints
β βββ dashboard/ # Main dashboard (client component)
β βββ onboarding/ # Onboarding flow
β βββ layout.tsx # Root layout with providers
β βββ page.tsx # Landing page
βββ components/
β βββ layouts/ # Layout components (MainLayout, Providers)
β βββ ui/ # shadcn/ui components
β βββ category-manager.tsx
β βββ sync-button.tsx
β βββ video-card.tsx
βββ db/
β βββ index.ts # Database connection
β βββ schemas/ # Drizzle schema definitions
β βββ auth.ts # User, session, account tables
β βββ helpers.ts # Shared helpers (lifecycle_dates, createId)
β βββ videos.ts # Videos, categories tables
β βββ index.ts # Schema exports
βββ hooks/ # Custom React hooks
βββ lib/
β βββ ai/ # AI utilities
β β βββ categorize.ts # Video categorization with Google AI
β βββ auth/ # Auth utilities
β β βββ client.ts # Client-side auth hooks
β β βββ server.ts # Better Auth config
β β βββ session.ts # Session helpers (requireSession)
β βββ validations/ # Zod schemas for API validation
β βββ constants.tsx # App constants & localStorage schemas
β βββ env.ts # Environment variable validation
β βββ errors.ts # AppError class & error utilities
β βββ logger.ts # Structured logging utility
β βββ site.ts # Site metadata config
β βββ utils.ts # Utility functions (cn, error helpers)
β βββ youtube.ts # YouTube API client
βββ types/
β βββ video.ts # Video type definitions (Database/Server/Client)
βββ workflows/ # Trigger.dev background jobs
βββ scheduled-sync.ts # Scheduled video sync
βββ sync-videos.ts # Video sync workflow
GET/POST /api/auth/[...all]- Better Auth catch-all route
GET /api/categories- Get all user categoriesPOST /api/categories- Create a new categoryGET /api/categories/[id]- Get category by IDDELETE /api/categories/[id]- Delete a category
GET /api/youtube/videos- Get paginated videos (with filters)GET /api/youtube/videos/counts- Get video counts by categoryPOST /api/youtube/sync- Trigger video syncPOST /api/youtube/full-sync- Full sync with categorization
POST /api/categorize- Categorize videos using AI
POST /api/onboarding/complete- Mark onboarding as complete
GET /api/sync-status- Get current sync status
- user - User accounts (managed by Better Auth)
- session - User sessions
- account - OAuth accounts (Google with YouTube scope)
- categories - User-defined video categories
- videos - Synced YouTube videos with category assignments
- Users have many categories (with default seeds on signup)
- Users have many videos (unique constraint on userId + youtubeId)
- Videos optionally belong to a category (nullable foreign key)
| Command | Purpose |
|---|---|
pnpm dev |
Start development server |
pnpm build |
Build for production |
pnpm start |
Start production server |
pnpm lint |
Run ESLint |
pnpm db:generate |
Generate Drizzle migrations from schema changes |
pnpm db:migrate |
Run pending migrations |
pnpm db:push |
Push schema directly (dev only) |
pnpm db:studio |
Open Drizzle Studio (database GUI) |
pnpm check-env |
Validate environment variables |
Path Aliases:
- Use
~/for imports fromsrc/
Error Handling:
- Use
AppErrorclass for consistent error responses - All API routes follow standard error handling pattern
Type Safety:
- Videos have three type layers:
DatabaseVideo,Video,SerializedVideo - Always serialize before sending to client
Logging:
- Use structured logger (
~/lib/logger) instead ofconsole.log
Ensure all required environment variables are set in your production environment:
DATABASE_URL
GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET
GOOGLE_GENERATIVE_AI_API_KEY
BETTER_AUTH_SECRET
BETTER_AUTH_URL # Your production URL
TRIGGER_SECRET_KEY
NODE_ENV=production# Build the application
pnpm build
# Run migrations
pnpm db:migrate
# Start production server
pnpm start- Push your code to GitHub
- Import project in Vercel
- Add environment variables
- Deploy
The app will automatically build and deploy on every push to main.
Deploy your background workflows:
npx trigger.dev@latest deployContributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run linting (
pnpm lint) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is private and proprietary.
- Website: ballroom.ygkr.live
- Author: Yash Gourav Kar
- Twitter: @YashGouravKar1
- Built with Next.js
- UI components from shadcn/ui
- Authentication by Better Auth
- Background jobs powered by Trigger.dev
Made with β€οΈ for organizing YouTube videos