# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview **Soundboard V2** - A full-stack web application for managing and playing sound files with streaming capabilities. ### Core Features - **Sound Management**: Upload, organize, and play sound files - **Stream Processing**: Download and process audio from YouTube, SoundCloud, and other platforms using yt-dlp - **Music Player**: Full-featured VLC-based music player with playlist support - **Real-time Updates**: WebSocket support for live player state synchronization - **Multi-Provider OAuth**: Google and GitHub authentication with JWT tokens - **Credits System**: Usage tracking with tiered subscription plans - **Sound Normalization**: Automatic audio processing and normalization ## Project Structure This is a full-stack application with separate backend (Flask) and frontend (React) components: - **Backend** (`backend/`): Flask application with clean architecture - Entry point: `main.py` - App structure: `app/` with separate `routes/` and `services/` directories - Business logic in services, routes handle HTTP concerns only - Uses modern Python tooling (Black, Ruff, pytest) - CORS enabled for frontend integration - Python 3.12+ required - Real-time features via Flask-SocketIO - **Frontend** (`frontend/`): React application with Vite - Uses React 19, TypeScript, React Router v7, and Tailwind CSS v4 - Shadcn/ui components with consistent sidebar layout for authenticated users - Built with Vite for fast development and production builds - Real-time updates via Socket.IO client ## Development Commands ### Backend (Flask) ```bash cd backend uv run python main.py # Run the Flask development server with SocketIO (localhost:5000) uv run pytest # Run tests uv run pytest tests/ # Run specific test directory uv run ruff check # Lint code uv run ruff format # Format code uv run black . # Format code (alternative to ruff format) # Database commands uv run python migrate_db.py init-db # Initialize database tables and seed data uv run python migrate_db.py reset-db # Reset database (drop and recreate all tables with seed data) uv run flask --app app db init # Initialize migrations (first time only) uv run flask --app app db migrate # Create new migration uv run flask --app app db upgrade # Apply migrations ``` #### Key Dependencies - **yt-dlp**: YouTube and streaming platform downloader - **python-vlc**: VLC media player Python bindings - **flask-socketio**: Real-time WebSocket communication - **ffmpeg-python**: Audio processing and normalization - **apscheduler**: Background task scheduling #### Environment Variables ```bash # Required for sessions and JWT export SECRET_KEY="your_secret_key_for_sessions" export JWT_SECRET_KEY="your_jwt_secret_key" # OAuth Providers (configure as needed) # Google OAuth export GOOGLE_CLIENT_ID="your_google_client_id" export GOOGLE_CLIENT_SECRET="your_google_client_secret" # GitHub OAuth export GITHUB_CLIENT_ID="your_github_client_id" export GITHUB_CLIENT_SECRET="your_github_client_secret" # Stream Processing (optional - defaults provided) export STREAM_MAX_CONCURRENT="2" # Number of concurrent downloads ``` ### Frontend (React/Vite) ```bash cd frontend bun dev # Start development server (preferred) on localhost:3000 # Alternative package managers: npm run dev / yarn dev / pnpm dev bun run build # Build for production bun run start # Start production server bun run lint # Run ESLint ``` #### Key Dependencies - **@number-flow/react**: Animated number transitions - **socket.io-client**: Real-time WebSocket communication - **next-themes**: Dark/light theme support - **react-router**: Client-side routing (v7) - **lucide-react**: Icon library - **@radix-ui**: Headless UI components ## Code Style ### Backend - Line length: 80 characters - Uses Ruff with comprehensive rule set (ALL rules enabled with specific ignores) - Black formatting enforced - Ignores: D100 (module docstrings), D104 (package docstrings) ### Frontend - TypeScript with strict configuration - ESLint with Next.js configuration - Tailwind CSS for styling - Uses Geist font family (sans and mono variants) ## Backend Architecture The Flask backend follows a clean architecture pattern: - **Routes** (`app/routes/`): Handle HTTP requests/responses, delegate to services - **Services** (`app/services/`): Contain all business logic, pure functions when possible - **Application Factory** (`app/__init__.py`): Creates and configures Flask app Key principles: - Routes should be thin - only handle HTTP concerns - Business logic lives in services - Services are testable in isolation - Clear separation of concerns ## Authentication The backend implements multi-provider OAuth authentication (Google, GitHub) with JWT tokens using Flask-JWT-Extended: ### Available Endpoints **OAuth Authentication:** - `GET /api/auth/providers` - Get list of available OAuth providers - `GET /api/auth/login/` - Initiate OAuth login for specified provider (google, github) - `GET /api/auth/callback/` - Handle OAuth callback from specified provider **Password Authentication:** - `POST /api/auth/register` - Register new user with email, password, and name - `POST /api/auth/login` - Login with email and password **Session Management:** - `GET /api/auth/logout` - Logout current user (clears cookies) - `GET /api/auth/me` - Get current user information (requires JWT) - `POST /api/auth/refresh` - Refresh access token using refresh token **Multi-Provider Management:** - `GET /api/auth/link/` - Link additional OAuth provider to current user (requires JWT) - `GET /api/auth/link/callback/` - Handle OAuth callback for linking provider - `DELETE /api/auth/unlink/` - Unlink OAuth provider from current user (requires JWT) **API Token Management:** - `POST /api/auth/regenerate-api-token` - Generate new API token for current user (requires JWT) **Example Endpoints:** - `GET /api/protected` - Example protected endpoint (requires authentication) - `GET /api/api-protected` - Example endpoint that accepts JWT or API token authentication - `GET /api/admin` - Example admin-only endpoint (requires admin role) - `GET /api/use-credits/` - Example endpoint that costs 5 credits to use - `GET /api/expensive-operation` - Example endpoint that costs 10 credits to use ### Flask-JWT-Extended Features - **Access Token**: Short-lived (15 minutes), contains user identity and claims - **Refresh Token**: Long-lived (7 days), used to generate new access tokens - **HTTP-only Cookies**: Automatic secure storage with Flask-JWT-Extended - **Built-in Security**: CSRF protection, secure cookie settings - **Cookie Paths**: Access tokens work on `/api/`, refresh tokens on `/api/auth/refresh` ### Usage Flow **Password Authentication:** 1. **Register**: `POST /api/auth/register` with `{"email": "...", "password": "...", "name": "..."}` 2. **Login**: `POST /api/auth/login` with `{"email": "...", "password": "..."}` 3. JWT tokens are automatically set as HTTP-only cookies 4. Access protected endpoints - Flask-JWT-Extended handles token validation **OAuth Authentication:** 1. Call `/api/auth/providers` to get available OAuth providers 2. Call `/api/auth/login/` to initiate OAuth flow (e.g., `/api/auth/login/google` or `/api/auth/login/github`) 3. Redirect user to the returned OAuth URL 4. Provider redirects back to `/api/auth/callback/` with authorization code 5. JWT tokens are automatically set as HTTP-only cookies 6. Access protected endpoints - Flask-JWT-Extended handles token validation **Multi-Provider Management:** 1. User authenticates with password or OAuth provider 2. Call `/api/auth/link/` to link additional OAuth providers 3. Users can sign in with password or any linked OAuth provider 4. Call `DELETE /api/auth/unlink/` to remove OAuth providers (minimum 1 auth method required) 5. User data includes `providers` array showing all available authentication methods ### Authentication Components - **User Model**: Stores user profile information (email, name, picture, role) - **UserOAuth Model**: Stores provider-specific authentication data - **AuthService**: Handles multi-provider OAuth flow and Flask-JWT-Extended integration - **TokenService**: Simplified token generation using Flask-JWT-Extended - **OAuthProviderRegistry**: Manages available OAuth providers based on environment variables - **OAuthProvider**: Abstract base class for OAuth providers (Google, GitHub, etc.) - **Decorators**: `@require_auth`, `@require_admin`, `@require_role()` for access control - **Flask-JWT-Extended**: Handles cookie management, validation, and security ### Database Schema - **users** table: Core user information (id, email, name, picture, password_hash, role, is_active, api_token, api_token_expires_at, timestamps) - **user_oauth** table: Provider-specific data (user_id, provider, provider_id, email, name, picture) - **Relationships**: One user can have multiple OAuth providers, enabling flexible authentication - **Authentication**: Users can authenticate via password OR OAuth providers (or both) - **User Status**: Users have an `is_active` field (default: true) for account management - **API Tokens**: Each user automatically gets an API token with no expiration for programmatic access - **Roles**: First user gets "admin" role, subsequent users get "user" role by default ### Supported Authentication Methods - **Password**: Traditional email/password authentication with Werkzeug secure hashing - **Google OAuth**: OpenID Connect with `openid email profile` scopes - **GitHub OAuth**: OAuth 2.0 with `user:email` scope to access user profile and email - **API Token**: Long-lived tokens for programmatic access (no expiration by default) ### Role-Based Access Control - **Admin Role**: First user automatically gets admin privileges - **User Role**: Default role for all subsequent users - **Role Assignment**: Automatic during registration (password or OAuth) - **Access Control**: Use decorators to protect routes by role - **JWT Integration**: Role included in JWT tokens for stateless authorization ### Available Authentication Decorators - `@require_auth` - Requires authentication (JWT or API token) - `@require_role("role_name")` - Requires specific role (chainable with require_auth) - `@require_credits(amount)` - Requires and deducts specified credits from user ### Multi-Authentication Benefits - **Flexible Registration**: Register with email/password or OAuth providers - **Account Linking**: Link multiple authentication methods to one account - **Flexible Sign-in**: Sign in with password, Google, or GitHub - **API Access**: Programmatic access via API tokens for scripts and applications - **Account Recovery**: Multiple authentication options prevent lockout - **Data Consistency**: Single user profile updated from any authentication method - **Security**: Werkzeug secure password hashing and OAuth security best practices - **Role-Based Access**: Fine-grained permission control with automatic admin assignment ### API Token Usage **Getting Your API Token:** 1. Authenticate via password or OAuth to get JWT tokens 2. Call `POST /api/auth/regenerate-api-token` to get a new API token 3. Use the returned token for programmatic access **Using API Tokens:** ```bash # Include API token in Authorization header curl -H "Authorization: Bearer YOUR_API_TOKEN" http://localhost:5000/api/api-protected # Alternative format curl -H "Authorization: Token YOUR_API_TOKEN" http://localhost:5000/api/api-protected ``` **API Token Features:** - **No Expiration**: Tokens don't expire by default (can be configured per token) - **Regeneration**: Users can regenerate tokens at any time via `/api/auth/regenerate-api-token` - **Automatic Creation**: New tokens generated automatically during user registration - **Role Support**: Tokens inherit user's role for role-based access control - **Security**: 32-byte URL-safe tokens using `secrets.token_urlsafe()` ## Plan System The backend includes a subscription plan system that assigns users to different plans with varying credit limits: ### Available Plans - **Free Plan** (`free`): 25 credits (75 max) - Default plan for new users - **Premium Plan** (`premium`): 50 credits (150 max) - Enhanced features with increased limits - **Pro Plan** (`pro`): 100 credits (300 max) - Full access with unlimited usage ### Plan Assignment - **First User**: Automatically assigned to Pro plan with admin role - **Subsequent Users**: Automatically assigned to Free plan with user role - **Plan Information**: Included in all authentication responses (login, register, /me endpoint) ### Database Schema - **plans** table: id, code, name, description, credits, max_credits - **users.plan_id**: Foreign key to plans table (required) - **users.credits**: Current user credits (initialized from plan.credits) - **Relationship**: Each user belongs to exactly one plan ### Automatic Initialization The plan system is automatically initialized when the Flask app starts: - **Database Creation**: `db.create_all()` creates all tables including plans and users - **Plan Seeding**: Automatically seeds the three default plans if the plans table is empty - **User Migration**: Automatically assigns plans and credits to existing users who don't have them - **First User**: Gets Pro plan (100 credits) and admin role automatically - **Subsequent Users**: Get Free plan (25 credits) and user role automatically No manual migration scripts are needed - everything happens automatically on app startup. ### Plan Information in API Responses All user data returned by authentication endpoints includes plan and credits information: ```json { "user": { "id": "1", "email": "user@example.com", "name": "User Name", "role": "user", "credits": 25, "plan": { "id": 1, "code": "free", "name": "Free Plan", "description": "Basic features with limited usage", "credits": 25, "max_credits": 75 } } } ``` ### Credits System - **User Credits**: Each user has a `credits` field tracking their current available credits - **Initial Credits**: Set to the plan's `credits` value when user is created - **Plan Credits**: The default credits amount for the plan (what new users get) - **Max Credits**: The maximum credits a user on this plan can have - **Authentication**: Credits are included in JWT tokens and all auth responses ### Credit Usage Decorator Use the `@require_credits(amount)` decorator to protect endpoints that consume credits: ```python from app.services.decorators import require_auth, require_credits @bp.route("/ai-generation") @require_auth @require_credits(10) # Costs 10 credits to use def ai_generation(): """AI generation endpoint that costs 10 credits.""" return {"result": "AI generated content"} ``` **Features:** - **Automatic Deduction**: Credits are deducted from user's balance before endpoint execution - **Insufficient Credits**: Returns HTTP 402 (Payment Required) with clear error message - **Database Updates**: Credits are updated in real-time in the database - **Authentication**: Works with both JWT and API token authentication - **Error Handling**: If endpoint fails, credits are still deducted (transaction-like behavior) **Example Error Response:** ```json { "error": "Insufficient credits. Required: 10, Available: 5" } ``` ## Soundboard System The application includes a comprehensive sound management system with multiple sound types and real-time playback capabilities. ### Sound Types - **SDB (Soundboard)**: Traditional soundboard effects and clips stored in `sounds/soundboard/` - **SAY (Text-to-Speech)**: Generated speech audio files stored in `sounds/say/` - **STR (Stream)**: Downloaded audio from streaming platforms stored in `sounds/stream/` ### Database Models #### Sound Model - **Core fields**: id, type, name, filename, duration, size, hash - **Normalization**: normalized_filename, normalized_duration, normalized_size, normalized_hash - **Properties**: is_normalized, is_music, is_deletable, play_count - **Relationships**: playlist_sounds, streams - **Methods**: increment_play_count(), set_normalized_info(), find_by_hash() #### Stream Model - **Metadata**: service, service_id, url, title, track, artist, album, genre - **Status tracking**: status (pending/processing/completed/failed), error - **Relationships**: Links to Sound model when processing completes - **Constraints**: Unique constraint on service + service_id #### Playlist Models - **Playlist**: id, name, description, genre, user_id, is_main, is_current - **PlaylistSound**: Junction table with playlist_id, sound_id, order - **Methods**: add_sound(), find_current_playlist(), find_main_playlist() ### API Endpoints #### Soundboard Routes (`/api/soundboard/`) - `GET /sounds` - Get all soundboard sounds with type filtering - `POST /sounds//play` - Play a sound (costs 1 credit) - `POST /stop-all` - Stop all currently playing sounds - `POST /force-stop` - Force stop with aggressive cleanup - `GET /status` - Get current playback status #### Stream Routes (`/api/stream/`) - `POST /add-url` - Add streaming URL to processing queue #### Music Player Routes (`/api/player/`) - `GET /state` - Get current player state - `POST /play` - Start playback - `POST /pause` - Pause playback - `POST /stop` - Stop playback - `POST /next` - Skip to next track - `POST /previous` - Skip to previous track - `POST /seek` - Seek to position (0.0-1.0) - `POST /volume` - Set volume (0-100) - `POST /mode` - Set play mode (continuous/loop-playlist/loop-one/random/single) - `POST /playlist` - Load playlist into player - `POST /play-track` - Play track at specific index #### Admin Routes (`/api/admin/`) - Sound management endpoints for admin users - User management endpoints ### Services #### StreamProcessingService - **Queue-based processing**: Multi-threaded download queue with configurable concurrency - **Metadata extraction**: Uses yt-dlp to extract video/audio metadata - **Download processing**: Downloads audio in opus format with thumbnails - **File management**: Moves files to organized directory structure - **Database integration**: Creates Sound entries and links to Stream records - **Playlist integration**: Automatically adds processed sounds to main playlist - **Normalization**: Triggers automatic audio normalization after download #### MusicPlayerService - **VLC integration**: Uses python-vlc bindings for audio playback - **Playlist management**: Full playlist support with multiple play modes - **Real-time sync**: Background thread syncs player state with VLC - **Play tracking**: Tracks listening time and play counts (20% completion threshold) - **WebSocket updates**: Emits real-time player state via SocketIO - **Multi-format support**: Plays normalized and original audio files #### VLCService - **Process management**: Handles multiple VLC instances for sound effects - **Force stop capabilities**: Aggressive cleanup for stuck processes - **Sound tracking**: Records play counts and user interactions #### SoundNormalizerService - **Audio processing**: FFmpeg-based normalization with ReplayGain - **Two-pass processing**: Optional high-quality normalization - **Batch operations**: Queue-based processing for multiple files #### SocketIOService - **Real-time communication**: WebSocket server for live updates - **Player state sync**: Broadcasts player state changes to all clients - **Sound event notifications**: Play count updates and status changes ### File Organization ``` sounds/ ├── soundboard/ # Original soundboard files ├── say/ # Text-to-speech files ├── stream/ # Downloaded streaming audio │ └── thumbnails/ # Video thumbnails ├── normalized/ # Normalized versions │ ├── soundboard/ │ ├── say/ │ └── stream/ └── temp/ # Temporary download files ``` ### Real-time Features #### WebSocket Events - **player_state_update**: Complete player state including current track, position, volume - **sound_play_count_changed**: When sound play counts are updated - **stream_processing_update**: Status updates for stream downloads #### Player State Structure ```json { "is_playing": true, "current_time": 45000, "duration": 180000, "volume": 80, "play_mode": "continuous", "current_track": { "id": 123, "title": "Song Title", "artist": "Artist Name", "duration": 180000, "thumbnail": "http://localhost:5000/api/sounds/str/thumbnails/image.jpg", "service_url": "https://youtube.com/watch?v=...", "type": "STR" }, "current_track_index": 2, "playlist": [...], "playlist_id": 1 } ``` ### Stream Processing Workflow 1. **URL Submission**: User submits streaming URL via `/api/stream/add-url` 2. **Validation**: Check for duplicate URLs and extract basic metadata 3. **Queue Addition**: Add stream to processing queue with "pending" status 4. **Background Processing**: Worker thread processes stream: - Extract full metadata with yt-dlp - Download audio file (opus format preferred) - Download thumbnail if available - Move files to organized directory structure - Create Sound database entry - Link Stream to Sound - Add to main playlist - Trigger normalization 5. **Status Updates**: Stream status updated throughout process 6. **Completion**: Stream marked as "completed" with sound_id ### Play Modes - **single**: Play one track and stop - **continuous**: Play through playlist once - **loop-playlist**: Repeat entire playlist - **loop-one**: Repeat current track - **random**: Random track selection ### Error Handling - **Stream processing errors**: Detailed error messages stored in Stream.error - **VLC playback errors**: Graceful fallbacks and process cleanup - **File system errors**: Validation and cleanup of incomplete downloads - **Database errors**: Transaction rollbacks and error logging