openapi: 3.0.3
info:
  title: PubfuseNebula API
  version: 1.3.0
  description: |
    Pubfuse Streaming Platform API - Complete endpoint documentation with HMAC and JWT authentication.
    
    **Latest Updates (January 2026):**
    - ✅ Server-based ad frequency settings (GET /api/admin/settings) - Public endpoint for fetching ad configuration
    - ✅ Ad frequency configuration (default: 15 minutes = ads at :00, :15, :30, :45 on the hour)
    - ✅ Ad countdown toggle (5-second countdown before ads)
    - ✅ Preroll ad toggle (enable/disable preroll on stream start)
    - ✅ Google IMA (Interactive Media Ads) integration for ad delivery
    - ✅ Ad management settings at SDK client level (enableAdFree, preroll, intervals)
    - ✅ Event behavior options for scheduled events (onFileCompleted, onFileNotStarted)
    - ✅ Per-event ad settings (preroll, intervals, skip for paid users)
    - ✅ Profile endpoints (GET /api/users/profile, /api/users/dashboard) now use raw SQL
    - ✅ All social stats working (followers_count, following_count, streams_count)
    - ✅ Individual count endpoints for fallback (/api/users/followers/count, /api/users/following/count)
    - ✅ Improved error handling in JWT middleware (no error masking)
    - ✅ Follow/Unfollow endpoints fixed with raw SQL implementation
    - ✅ Idempotent operations (safe to call multiple times)
    - ✅ Self-follow prevention
    - ✅ Contact sync validation (isPubfuseUser only for existing users)
    - ✅ Real-time event debug endpoint (GET /api/events/:id/debug) with playback status monitoring
    - ✅ Enhanced scheduled event file repeat functionality with cooldown period
    - ✅ LiveKit Ingress status tracking and playback issue detection
    - ✅ Ad tracking endpoints (POST /api/broadcasts/{id}/ad/view, /api/broadcasts/{id}/ad/impression)
    - ✅ Session details endpoint (GET /api/broadcasts/{id}/details) for drill-down metrics
    - ✅ User reports endpoint (GET /api/reports/user/{userId}) with daily/weekly/monthly/yearly periods
    - ✅ What-if scenario calculator (POST /api/reports/user/{userId}/whatif) for payout predictions
    - ✅ Report export endpoint (GET /api/reports/user/{userId}/export) for CSV/PDF export
    - ✅ Fixed follower/following counts in user dashboard endpoint
    - ✅ Music User Linking - Link pubfuse.com users to pubfu.se music platform for unified account management
    - ✅ Music User Search - Search for existing pubfu.se users by email or nickname
    - ✅ Link to Existing Account - Link to existing pubfu.se accounts with email matching and password verification
    - ✅ User Interactions - Flexible system for tracking likes, gifts, sparks, and other interactions with any entity type (broadcast sessions, projects, tracks, users, etc.)
    - ✅ Music proxy CORS - pubfu.se (PubfuseNebula) allows CORS from pubfuse.com; proxy sends Content-Type application/json and Authorization Bearer <music-token> to pubfu.se.
    - ✅ Contact API (POST /api/contact) - Public endpoint for contact form submissions from web, iOS, and Android; no auth required.
    
    All endpoints tested and working in production.
servers:
  - url: http://localhost:8080
    description: Local development server
  - url: https://www.pubfuse.com
    description: Production server (WORKING)

paths:
  # Root and Health Endpoints
  /:
    get:
      operationId: rootOk
      summary: Root endpoint
      responses:
        '200':
          description: OK
          content:
            text/html:
              schema:
                type: string
  /health:
    get:
      operationId: health
      summary: Health check
      responses:
        '200':
          description: Healthy
  /health/db:
    get:
      operationId: healthDb
      summary: Database health check
      responses:
        '200':
          description: Database connection status
          content:
            application/json:
              schema:
                type: object
  /egress-player:
    get:
      operationId: egressPlayer
      summary: LiveKit Egress player page
      description: |
        Serves an HTML page that plays media files for LiveKit WebEgress.
        This endpoint is used by LiveKit Egress to render and publish media content into LiveKit rooms.
        
        **Parameters:**
        - `file` (required): URL of the media file to play (HTTP/HTTPS URL)
        - `type` (required): Content type - either "audio" or "video"
        
        **Usage:**
        This endpoint is called by LiveKit WebEgress when starting a scheduled event.
        The HTML page automatically plays the specified media file in a loop, which is then
        captured by LiveKit and published to the specified room.
        
        **Example URL:**
        `https://www.pubfuse.com/egress-player?file=https://www.pubfuse.com/userfiles/.../audio.mp3&type=audio`
      parameters:
        - in: query
          name: file
          required: true
          schema:
            type: string
            format: uri
          description: URL of the media file to play (must be HTTP/HTTPS URL)
        - in: query
          name: type
          required: true
          schema:
            type: string
            enum: [audio, video]
          description: Content type - either "audio" or "video"
      responses:
        '200':
          description: HTML player page
          content:
            text/html:
              schema:
                type: string
        '400':
          description: Missing or invalid parameters
  /ads.txt:
    get:
      operationId: adsTxt
      summary: Ads.txt file
      responses:
        '200':
          description: Ads.txt content
          content:
            text/plain:
              schema:
                type: string

  # Configuration Endpoints
  /config/ice.json:
    get:
      operationId: getIceConfig
      summary: Get ICE servers for WebRTC
      responses:
        '200':
          description: ICE configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ICEConfig'
  /config/ads.json:
    get:
      operationId: getAdsConfig
      summary: Get ads configuration
      responses:
        '200':
          description: Ads configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdConfig'
  /config/version.json:
    get:
      operationId: getVersionConfig
      summary: Get version information
      responses:
        '200':
          description: Version configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VersionConfig'

  # Web Pages
  /docs:
    get:
      operationId: docs
      summary: API documentation page
      responses:
        '200':
          description: Docs page
          content:
            text/html:
              schema:
                type: string
  /live-metrics:
    get:
      operationId: liveMetrics
      summary: Live metrics dashboard
      responses:
        '200':
          description: Live metrics page
          content:
            text/html:
              schema:
                type: string
  /live-metrics-grid:
    get:
      operationId: liveMetricsGrid
      summary: Live metrics grid view
      responses:
        '200':
          description: Live metrics grid page
          content:
            text/html:
              schema:
                type: string
  /admin:
    get:
      operationId: adminDashboard
      summary: Admin dashboard
      responses:
        '200':
          description: Admin dashboard page
          content:
            text/html:
              schema:
                type: string
  /admin/sdk-clients:
    get:
      operationId: adminSdkClients
      summary: SDK clients management
      responses:
        '200':
          description: SDK clients page
          content:
            text/html:
              schema:
                type: string
  /admin/users:
    get:
      operationId: adminUsers
      summary: Users management
      responses:
        '200':
          description: Users page
          content:
            text/html:
              schema:
                type: string
  /pubfuse-admin:
    get:
      operationId: pubfuseAdmin
      summary: Pubfuse admin dashboard
      responses:
        '200':
          description: Pubfuse admin page
          content:
            text/html:
              schema:
                type: string
  /pubfuse-admin/login:
    get:
      operationId: pubfuseAdminLogin
      summary: Pubfuse admin login
      responses:
        '200':
          description: Pubfuse admin login page
          content:
            text/html:
              schema:
                type: string
  /pubfuse-admin/analytics:
    get:
      operationId: pubfuseAdminAnalytics
      summary: Pubfuse admin analytics
      responses:
        '200':
          description: Pubfuse admin analytics page
          content:
            text/html:
              schema:
                type: string
  /pubfuse-admin/settings:
    get:
      operationId: pubfuseAdminSettings
      summary: Pubfuse admin settings
      responses:
        '200':
          description: Pubfuse admin settings page
          content:
            text/html:
              schema:
                type: string
  /login:
    get:
      operationId: loginPage
      summary: User login page
      responses:
        '200':
          description: Login page
          content:
            text/html:
              schema:
                type: string
  /signup:
    get:
      operationId: signupPage
      summary: User signup page
      responses:
        '200':
          description: Signup page
          content:
            text/html:
              schema:
                type: string
  /sdk-signup:
    get:
      operationId: sdkSignupPage
      summary: SDK signup page
      responses:
        '200':
          description: SDK signup page
          content:
            text/html:
              schema:
                type: string
  /dashboard:
    get:
      operationId: dashboard
      summary: User dashboard
      responses:
        '200':
          description: Dashboard page
          content:
            text/html:
              schema:
                type: string
  /auth:
    get:
      operationId: authPage
      summary: Authentication page
      responses:
        '200':
          description: Auth page
          content:
            text/html:
              schema:
                type: string
  /sdk-docs:
    get:
      operationId: sdkDocs
      summary: SDK documentation
      responses:
        '200':
          description: SDK docs page
          content:
            text/html:
              schema:
                type: string
  # MCP (Model Context Protocol) Server Endpoints
  /mcp/initialize:
    post:
      operationId: mcpInitialize
      summary: Initialize MCP connection
      description: |
        Initialize a connection to the MCP server. Returns server capabilities and version information.
        This is the first call that should be made when connecting to the MCP server.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                protocolVersion:
                  type: string
                  example: "2024-11-05"
                capabilities:
                  type: object
                clientInfo:
                  type: object
                  properties:
                    name:
                      type: string
                    version:
                      type: string
      responses:
        '200':
          description: MCP initialization response
          content:
            application/json:
              schema:
                type: object
                properties:
                  protocolVersion:
                    type: string
                  capabilities:
                    type: object
                  serverInfo:
                    type: object
                    properties:
                      name:
                        type: string
                      version:
                        type: string
  /mcp/tools/list:
    get:
      operationId: mcpToolsList
      summary: List available MCP tools
      description: |
        Returns a list of all available MCP tools (API operations) that can be called.
        Tools are automatically generated from the MCPController.
      responses:
        '200':
          description: List of available tools
          content:
            application/json:
              schema:
                type: object
                properties:
                  tools:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                          description: Tool name
                        description:
                          type: string
                          description: Tool description
                        inputSchema:
                          type: object
                          description: JSON schema for tool parameters
  /mcp/tools/call:
    post:
      operationId: mcpToolsCall
      summary: Execute an MCP tool
      description: |
        Execute a specific MCP tool with the provided arguments.
        Returns the result of the tool execution.
        
        **Authentication:**
        - JWT tokens from `/api/users/login` are supported
        - Include token in `Authorization: Bearer <token>` header (recommended)
        - Some tools also accept `token` parameter in arguments as fallback
        - Tools that require authentication will return 401 if no valid token is provided
      security:
        - bearerAuth: []
      parameters:
        - in: header
          name: Authorization
          required: false
          schema:
            type: string
          description: JWT token from username/password login (Bearer token)
          example: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - params
              properties:
                params:
                  type: object
                  required:
                    - name
                  properties:
                    name:
                      type: string
                      description: Name of the tool to execute
                      example: "get_user"
                    arguments:
                      type: object
                      description: Tool-specific arguments
                      example:
                        userId: "123e4567-e89b-12d3-a456-426614174000"
      responses:
        '200':
          description: Tool execution result
          content:
            application/json:
              schema:
                type: object
                properties:
                  content:
                    type: array
                    items:
                      type: object
                      properties:
                        type:
                          type: string
                          example: "text"
                        text:
                          type: string
                          description: Result data as JSON string
        '400':
          description: Invalid tool name or arguments
  /mcp/resources/list:
    get:
      operationId: mcpResourcesList
      summary: List available MCP resources
      description: |
        Returns a list of all available MCP resources (data sources) that can be read.
      responses:
        '200':
          description: List of available resources
          content:
            application/json:
              schema:
                type: object
                properties:
                  resources:
                    type: array
                    items:
                      type: object
                      properties:
                        uri:
                          type: string
                          example: "pubfuse://active-streams"
                        name:
                          type: string
                        description:
                          type: string
                        mimeType:
                          type: string
                          example: "application/json"
  /mcp/resources/read:
    get:
      operationId: mcpResourcesRead
      summary: Read an MCP resource
      description: |
        Read the content of a specific resource by URI.
      parameters:
        - in: query
          name: uri
          required: true
          schema:
            type: string
          description: Resource URI
          example: "pubfuse://active-streams"
      responses:
        '200':
          description: Resource content
          content:
            application/json:
              schema:
                type: object
                properties:
                  contents:
                    type: array
                    items:
                      type: object
                      properties:
                        uri:
                          type: string
                        mimeType:
                          type: string
                        text:
                          type: string
                          description: Resource content as JSON string
        '404':
          description: Resource not found
  /api-strategy:
    get:
      operationId: apiStrategy
      summary: API strategy page
      responses:
        '200':
          description: API strategy page
          content:
            text/html:
              schema:
                type: string
  /pricing:
    get:
      operationId: pricing
      summary: Pricing page
      responses:
        '200':
          description: Pricing page
          content:
            text/html:
              schema:
                type: string
  /features:
    get:
      operationId: features
      summary: Features page
      responses:
        '200':
          description: Features page
          content:
            text/html:
              schema:
                type: string
  /apps:
    get:
      operationId: apps
      summary: Apps page
      responses:
        '200':
          description: Apps page
          content:
            text/html:
              schema:
                type: string
  /about:
    get:
      operationId: about
      summary: About page
      responses:
        '200':
          description: About page
          content:
            text/html:
              schema:
                type: string
  /contact:
    get:
      operationId: contact
      summary: Contact page
      responses:
        '200':
          description: Contact page
          content:
            text/html:
              schema:
                type: string
  /api/contact:
    post:
      operationId: submitContact
      summary: Submit contact form
      description: |
        Public endpoint for contact form submissions. Used by the web contact page, iOS, and Android apps.
        No authentication required. Sends email to support when server email (e.g. Resend) is configured.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, message]
              properties:
                name:
                  type: string
                  description: Sender name (optional)
                email:
                  type: string
                  format: email
                  description: Sender email (required)
                message:
                  type: string
                  description: Message body (required)
            example:
              name: "Jane Doe"
              email: "jane@example.com"
              message: "I'd like to learn more about your API."
      responses:
        '200':
          description: Message accepted (and sent when email is configured)
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: true
        '400':
          description: Validation error (e.g. missing email or message)
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: "Email is required."
        '500':
          description: Server error (e.g. email send failed)
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: false
                  error:
                    type: string
  /terms:
    get:
      operationId: terms
      summary: Terms page
      responses:
        '200':
          description: Terms page
          content:
            text/html:
              schema:
                type: string

  # Legacy API Endpoints
  /api/streams:
    post:
      operationId: createStream
      summary: Create a new stream (legacy)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StreamCreateRequest'
      responses:
        '200':
          description: Stream created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StreamResponse'
  /api/streams/{id}:
    get:
      operationId: getStream
      summary: Get stream details (legacy)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Stream details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StreamResponse'
  /api/streams/{id}/tokens:
    post:
      operationId: mintToken
      summary: Mint stream tokens (legacy)
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Token minted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
  /api/streams/webhook:
    post:
      operationId: webhook
      summary: Stream webhook (legacy)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Webhook processed
  /api/streams/{id}/watch:
    get:
      operationId: watchStream
      summary: Watch stream page
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Stream watch page
          content:
            text/html:
              schema:
                type: string

  # WebSocket Endpoints
  /ws/streams/{id}/watch:
    get:
      operationId: watchWS
      summary: Unified WebSocket for video streaming, chat, reactions, and ad synchronization
      description: |
        WebSocket endpoint for real-time streaming features including WebRTC video streaming, 
        chat messages, live reactions, and ad synchronization. 
        
        **Connection URL:** `ws://pubfuse.com/ws/streams/{id}/watch` or `wss://pubfuse.com/ws/streams/{id}/watch`
        
        **Message Protocol:** All messages are JSON format with the following structure:
        ```json
        {
          "type": "message-type",
          "clientId": "unique-client-identifier",
          "message": "optional-content",
          "timestamp": 1234567890.123
        }
        ```
        
        **Supported Message Types:**
        - `viewer-hello`: Join stream as viewer
        - `welcome`: Server confirmation of join
        - `chat`: Chat messages
        - `reaction`: Live reactions (heart, like, etc.)
        - `sdp-offer`: WebRTC SDP offer for video streaming
        - `sdp-answer`: WebRTC SDP answer for video streaming
        - `ice-candidate`: WebRTC ICE candidate for NAT traversal
        - `ad-trigger`: Synchronized ad trigger
        - `ping`: Heartbeat to maintain connection
        - `pong`: Heartbeat response
        
        **Usage Flow:**
        1. Connect to WebSocket endpoint with valid stream ID
        2. Send `viewer-hello` message to join stream
        3. Receive `welcome` confirmation
        4. Handle incoming messages (chat, reactions, video, etc.)
        5. Send `ping` every 30 seconds to maintain connection
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: The UUID of the broadcast session (obtain from /api/sessions or /api/metrics/dashboard)
      responses:
        '101':
          description: WebSocket connection established for video streaming, chat, reactions, and ad synchronization
          content:
            application/json:
              schema:
                type: object
                properties:
                  type:
                    type: string
                    enum: [chat, reaction, viewer-hello, sdp-offer, sdp-answer, ice-candidate, ad-trigger]
                  message:
                    type: string
                  from:
                    type: string
                  timestamp:
                    type: string
                    format: date-time
                  data:
                    type: object
                    description: WebRTC signaling data or ad trigger information

  # Sessions API (Mobile App Compatible)
  /api/sessions:
    get:
      operationId: getSessionsDirect
      summary: Get available sessions (mobile app endpoint)
      description: Direct endpoint for mobile apps to browse available streaming sessions
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: status
          schema:
            type: string
            enum: [pending, active, paused, completed, error, cancelled, private]
            default: active
        - in: query
          name: visibility
          schema:
            type: string
            enum: [public, private]
            default: public
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Available sessions for mobile apps
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ExternalSessionResponse'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Authentication required"
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Database not available"
  
  # Phone Sessions API
  /api/sessions/phone:
    get:
      operationId: getPhoneSessions
      summary: Get phone call sessions
      description: Retrieve phone call sessions (isPhoneSession = true)
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Phone call sessions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ExternalSessionResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Internal server error"
  
  # Metrics Dashboard API
  /api/metrics/dashboard:
    get:
      operationId: getMetricsDashboard
      summary: Get comprehensive dashboard metrics
      description: |
        Retrieve comprehensive dashboard metrics including session statistics, viewer metrics, 
        revenue data, platform health, and top performing sessions. This endpoint provides 
        real-time analytics for the platform.
      tags:
        - Metrics
      responses:
        '200':
          description: Dashboard metrics retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionMetricsResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Database not available"

  # LiveKit Streaming API
  /api/streaming/providers:
    get:
      operationId: getStreamingProviders
      summary: Get available streaming providers
      description: |
        Retrieve a list of available streaming providers and their configuration status.
        Returns information about LiveKit SFU and WebRTC P2P providers.
      tags:
        - Streaming
      responses:
        '200':
          description: List of streaming providers
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/StreamingProvider'
        '500':
          description: Server error
  /api/streaming/sessions:
    post:
      operationId: createStreamingSession
      summary: Create a new streaming session
      description: |
        Create a new streaming session for live broadcasting. Returns session details including
        RTMP ingest URL, HLS playback URL, and LiveKit room information.
      tags:
        - Streaming
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateStreamingSessionRequest'
      responses:
        '200':
          description: Session created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StreamingSessionResponse'
        '401':
          description: Authentication required
        '400':
          description: Invalid request data
        '500':
          description: Server error
  /api/streaming/sessions/{id}:
    get:
      operationId: getStreamingSession
      summary: Get streaming session details
      description: |
        Retrieve details for a specific streaming session by ID.
        Supports both UUID format and short IDs (e.g., "r5", "f4").
      tags:
        - Streaming
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
          description: Session ID (UUID or short ID)
      responses:
        '200':
          description: Session details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StreamingSessionResponse'
        '404':
          description: Session not found
        '500':
          description: Server error
  /api/streaming/sessions/{id}/token:
    post:
      operationId: generateLiveKitToken
      summary: Generate LiveKit JWT access token
      description: |
        Generate a LiveKit-compatible JWT token for joining a streaming session.
        The token includes all required LiveKit claims and grants appropriate permissions
        based on the specified role (publisher, subscriber, or both).
        
        **Authentication:**
        - Requires `X-API-Key` header with your Pubfuse API key
        - Optional: `X-Signature` and `X-Timestamp` headers for HMAC authentication
        
        **Token Format:**
        - Returns a JWT string that can be used directly with LiveKit SDK
        - Token expires after 1 hour
        - Includes room name, participant identity, and role-based permissions
        
        **Session ID Support:**
        - Accepts both UUID format and short IDs (e.g., "r5", "f4")
        - Short IDs are resolved server-side to the session UUID
        - **For Scheduled Events**: Can accept LiveKit room name directly (UUID format)
          - If the ID is not found as a broadcast session ID, the server checks:
            1. If it's a scheduled event's `livekit_room_name`
            2. If it's a broadcast's `stream_id` (room name)
          - This allows scheduled events to connect using their event's room name
      tags:
        - Streaming
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
          description: Session ID (UUID or short ID)
        - in: header
          name: X-API-Key
          required: true
          schema:
            type: string
          description: Pubfuse API key
        - in: header
          name: X-Signature
          schema:
            type: string
          description: HMAC signature (optional, for enhanced security)
        - in: header
          name: X-Timestamp
          schema:
            type: string
          description: Unix timestamp (optional, for enhanced security)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenRequest'
      responses:
        '200':
          description: Token generated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LiveKitTokenResponse'
        '400':
          description: Invalid request (invalid role, userId format, or session ID)
        '401':
          description: Missing or invalid API key
        '404':
          description: Session not found
        '500':
          description: Server error
  /api/streaming/ice-config:
    get:
      operationId: getIceConfiguration
      summary: Get ICE server configuration for WebRTC
      description: |
        Retrieve ICE (Interactive Connectivity Establishment) server configuration
        for WebRTC connections. Includes STUN and TURN server information.
      tags:
        - Streaming
      responses:
        '200':
          description: ICE configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ICEConfig'
        '500':
          description: Server error
  /api/livekit/health:
    get:
      operationId: getLiveKitHealth
      summary: LiveKit server health check
      description: |
        Check the health and configuration status of the LiveKit server.
        Returns whether LiveKit is properly configured and available.
      tags:
        - Streaming
      responses:
        '200':
          description: Health status
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "ok"
                  provider:
                    type: string
                    example: "livekit"
                  server:
                    type: string
                    nullable: true
                    example: "wss://pubfusestream-ctq7dz1g.livekit.cloud"
                  configured:
                    type: boolean
                    example: true

  # Scheduled Events API
  /api/events:
    get:
      operationId: getEvents
      summary: Get scheduled events
      description: |
        Retrieve a list of scheduled events. Supports filtering by status.
        
        **Scheduled Events and LiveKit Room Names:**
        When a scheduled event starts, it uses the same unified logic as createBroadcast:
        - Creates a stream via StreamService.create() which generates streamId (UUID)
        - Saves streamId to broadcast_sessions.stream_id
        - Sets livekit_room_name in scheduled_events to match streamId
        - The livekit_room_name is the LiveKit room name for WebRTC connections
        - If broadcast_session_id exists but is missing stream_id, the scheduler updates it
        - This ensures consistency with manual broadcast creation from the website
      tags:
        - Events
      parameters:
        - in: query
          name: status
          schema:
            type: string
            enum: [scheduled, active, completed, cancelled]
          description: Filter events by status
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
          description: Maximum number of events to return
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
          description: Number of events to skip
      responses:
        '200':
          description: List of scheduled events
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ScheduledEventResponse'
        '500':
          description: Server error
  /api/events/{id}:
    get:
      operationId: getEvent
      summary: Get a specific scheduled event
      description: Retrieve details for a specific scheduled event by ID
      tags:
        - Events
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Event ID
      responses:
        '200':
          description: Event details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScheduledEventResponse'
        '404':
          description: Event not found
        '500':
          description: Server error
    put:
      operationId: updateEvent
      summary: Update a scheduled event
      description: |
        Update an existing scheduled event. Can update scheduled times, title, description, status, etc.
        When manually starting an event, set `scheduledStartTime` to the current time before calling start-video.
      tags:
        - Events
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Event ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateEventRequest'
      responses:
        '200':
          description: Event updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScheduledEventResponse'
        '401':
          description: Authentication required
        '404':
          description: Event not found
        '500':
          description: Server error
    delete:
      operationId: deleteEvent
      summary: Delete a scheduled event
      tags:
        - Events
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Event ID
      responses:
        '200':
          description: Event deleted successfully
        '401':
          description: Authentication required
        '404':
          description: Event not found
        '500':
          description: Server error
  /api/events/schedule:
    post:
      operationId: scheduleEvent
      summary: Schedule a new event
      description: Create a new scheduled event for automated media playback
      tags:
        - Events
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScheduleEventRequest'
      responses:
        '200':
          description: Event scheduled successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScheduledEventResponse'
        '401':
          description: Authentication required
        '400':
          description: Invalid request data
        '500':
          description: Server error
  /api/events/{id}/start-video:
    post:
      operationId: startVideoPlayback
      summary: Start video/audio playback for an event
      description: |
        Start LiveKit Egress for a scheduled event. This will:
        1. Create or update the broadcast session
        2. Start LiveKit Egress to stream the media file into the LiveKit room
        3. Update the event status to "active"
        
        **Note:** Before calling this endpoint, update the event's `scheduledStartTime` to the current time
        if manually starting an event.
        
        **LiveKit Egress:**
        - Uses LiveKit WebEgress to render an HTML page that plays the media file
        - The HTML page is served at `/egress-player?file={fileURL}&type={contentType}`
        - LiveKit captures the rendered page and publishes it to the room
      tags:
        - Events
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Event ID
      responses:
        '200':
          description: Video playback started successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScheduledEventResponse'
        '401':
          description: Authentication required
        '404':
          description: Event not found
        '500':
          description: Failed to start LiveKit Egress or update event
  /api/events/{id}/stop-video:
    post:
      operationId: stopVideoPlayback
      summary: Stop video/audio playback for an event
      description: |
        Stop LiveKit Egress for a scheduled event. This will:
        1. Stop the LiveKit Egress job
        2. Update the event status to "completed"
        3. Set the event's `scheduledEndTime` to the current time
        4. Update the broadcast session status to "completed"
        
        **Note:** This endpoint automatically sets `scheduledEndTime` to the current time.
      tags:
        - Events
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Event ID
      responses:
        '200':
          description: Video playback stopped successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScheduledEventResponse'
        '401':
          description: Authentication required
        '404':
          description: Event not found
        '500':
          description: Server error
  /api/events/{id}/debug:
    get:
      operationId: debugEventBroadcast
      summary: Get debug information for an event
      description: |
        Retrieve detailed real-time debug information about a scheduled event, including:
        - Event details and status
        - Broadcast session status
        - LiveKit room information and active tracks
        - LiveKit Ingress status and errors
        - File playback status and issues
        - Scheduler check times and repeat settings
        
        **Real-time Status:**
        - `hasActiveTracks`: Indicates if audio/video tracks are currently active in the LiveKit room
        - `ingressStatus`: Indicates if the LiveKit Ingress job is running
        - `playbackIssue`: Warning message if playback should be active but isn't
        - `nextSchedulerCheckTime`: When the scheduler will next check for file restart
        
        This endpoint is useful for troubleshooting scheduled events, detecting playback issues, and monitoring file repeat functionality.
      tags:
        - Events
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Event ID
      responses:
        '200':
          description: Debug information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventBroadcastDebugResponse'
        '401':
          description: Authentication required
        '404':
          description: Event not found
        '500':
          description: Server error

  # Message Sessions API
  /api/sessions/messages:
    get:
      operationId: getMessageSessions
      summary: Get message sessions
      description: Retrieve message sessions (isMessageSession = true)
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Message sessions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ExternalSessionResponse'
        '500':
          description: Server error

  # Stream Chat (stored in contact_history)
  /api/streams/{id}/chat:
    get:
      operationId: getStreamChat
      summary: Get chat messages for a stream
      description: |
        Returns chat messages persisted in contact_history for the given stream.
        If a broadcast ID is provided, the server looks up the associated stream_id
        from broadcast_sessions (fallback to broadcasts). Results include messages
        for related_broadcast_id (always) and related_stream_id when available.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
        - in: query
          name: limit
          schema:
            type: integer
            default: 200
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Stream chat messages
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
    post:
      operationId: postStreamChat
      summary: Post a chat message to a stream
      description: Persists a chat message to contact_history with related_stream_id. Uses authenticated user as sender.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                message:
                  type: string
                  example: "Hello everyone!"
              required:
                - message
      responses:
        '200':
          description: Created chat message
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Not Found"

  # Broadcast Chat (stored in contact_history)
  /api/broadcasts/{id}/chat:
    get:
      operationId: getBroadcastChat
      summary: Get chat messages for a broadcast
      description: Returns chat messages persisted in contact_history for the given broadcast. Also surfaces messages stored under the broadcast's stream_id when present.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
        - in: query
          name: limit
          schema:
            type: integer
            default: 200
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Broadcast chat messages
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
    post:
      operationId: postBroadcastChat
      summary: Post a chat message to a broadcast
      description: Persists a chat message to contact_history with related_broadcast_id (and related_stream_id when available). Uses authenticated user as sender.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                message:
                  type: string
                  example: "Hello everyone!"
              required:
                - message
      responses:
        '200':
          description: Created chat message
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Not Found"

  # iOS SDK Required Endpoints (Protected API)
  /api/v1/sessions:
    get:
      operationId: getSessions
      summary: List all sessions
      description: List all sessions with flexible authentication (API key or JWT token)
      security:
        - HMACSignature: []
        - BearerAuth: []
      parameters:
        - in: query
          name: status
          schema:
            type: string
            enum: [pending, active, paused, completed, error, cancelled, private]
            default: active
        - in: query
          name: visibility
          schema:
            type: string
            enum: [public, private]
            default: public
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of sessions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SessionResponse'
        '401':
          description: Authentication required (API key or JWT token)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Authentication required (JWT token or API key)"
        '500':
          description: Server error
    post:
      operationId: createSession
      summary: Create new session
      security:
        - HMACSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  description: Session title
                description:
                  type: string
                  description: Session description
              required:
                - title
      responses:
        '200':
          description: Session created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponse'
        '401':
          description: Invalid API key or signature
        '500':
          description: Server error
  /api/v1/sessions/{id}:
    get:
      operationId: getSessionById
      summary: Get specific session details
      security:
        - HMACSignature: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Session ID
      responses:
        '200':
          description: Session details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionDetailResponse'
        '401':
          description: Invalid API key or signature
        '404':
          description: Session not found
        '500':
          description: Server error
  /api/v1/users/login:
    post:
      operationId: userLogin
      summary: User authentication
      security:
        - HMACSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Login successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '401':
          description: Invalid credentials
  /api/v1/users/signup:
    post:
      operationId: userSignup
      summary: User registration
      security:
        - HMACSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignupRequest'
      responses:
        '200':
          description: Registration successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignupResponse'
  /api/v1/users/logout:
    post:
      operationId: userLogout
      summary: User logout
      security:
        - HMACSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LogoutRequest'
      responses:
        '200':
          description: Logout successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LogoutResponse'
  /api/v1/broadcasts:
    get:
      operationId: getBroadcasts
      summary: List all broadcasts
      security:
        - HMACSignature: []
      responses:
        '200':
          description: List of broadcasts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BroadcastListResponse'
    post:
      operationId: createBroadcast
      summary: Create a new broadcast
      description: |
        Creates a new broadcast session and automatically generates all required streaming credentials.
        
        **Unified Stream Creation Logic:**
        - Creates a stream via `StreamService.create()` which generates a unique `streamId` (UUID)
        - The `streamId` is used as the LiveKit room name for WebRTC connections
        - Saves `streamId` to `broadcast_sessions.stream_id` column in the database
        - Generates `streamKey` (UUID without dashes) for RTMP ingest
        - Creates `rtmpUrl`, `hlsUrl`, and `watchUrl` using the `streamId`
        - Returns complete `BroadcastResponse` with all fields populated
        
        **Important:** The `streamId` in the response is the LiveKit room name. Use this value when:
        - Connecting to LiveKit rooms (WebRTC)
        - Generating LiveKit JWT tokens
        - The `streamId` is stored in `broadcast_sessions.stream_id` and matches the LiveKit room name
        
        This same unified logic is used by:
        - Website `startBroadcast` functionality
        - Scheduled events scheduler
        - All broadcast creation endpoints
        
        **Response Fields:**
        - `id`: Broadcast session ID (use for status updates, heartbeats)
        - `streamId`: **LiveKit room name** (use for LiveKit connections)
        - `streamKey`: RTMP stream key (for external encoders)
        - `rtmpUrl`: RTMP ingest URL
        - `hlsUrl`: HLS playback URL
        - `watchUrl`: Web viewer URL
      security:
        - HMACSignature: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BroadcastCreateRequest'
      responses:
        '200':
          description: Broadcast created with all streaming credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BroadcastResponse'
  /api/v1/broadcasts/{id}:
    get:
      operationId: getBroadcast
      summary: Get specific broadcast details
      security:
        - HMACSignature: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Broadcast details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BroadcastDetailResponse'
  /api/v1/broadcasts/{id}/status:
    post:
      operationId: updateBroadcastStatus
      summary: Update broadcast status
      security:
        - HMACSignature: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StatusUpdateRequest'
      responses:
        '200':
          description: Status updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusUpdateResponse'
  /api/broadcasts/{id}/ad/view:
    post:
      operationId: trackAdView
      summary: Track ad view (preroll or midroll)
      description: |
        Track when an ad is viewed. Increments total_ad_views and either total_preroll_views 
        or total_midroll_views based on the ad type.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Broadcast session ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - adType
              properties:
                adType:
                  type: string
                  enum: [preroll, midroll]
                  description: Type of ad being viewed
                isImpression:
                  type: boolean
                  description: Whether to also track as impression
                  default: false
      responses:
        '200':
          description: Ad view tracked successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
        '400':
          description: Invalid ad type
        '404':
          description: Broadcast session not found
  /api/broadcasts/{id}/ad/impression:
    post:
      operationId: trackAdImpression
      summary: Track ad impression (when ad is loaded)
      description: |
        Track when an ad is loaded/impressed. Increments total_ad_impressions.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Broadcast session ID
      responses:
        '200':
          description: Ad impression tracked successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
        '404':
          description: Broadcast session not found
  /api/broadcasts/{id}/details:
    get:
      operationId: getBroadcastDetails
      summary: Get detailed session metrics
      description: |
        Get comprehensive session metrics for drill-down view. Includes all viewer, engagement, 
        ad, revenue, and time metrics with calculated ratios.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Broadcast session ID
      responses:
        '200':
          description: Session details retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BroadcastDetailsResponse'
        '404':
          description: Broadcast session not found
  /api/reports/user/{userId}:
    get:
      operationId: getUserReport
      summary: Get user report (daily/weekly/monthly/yearly)
      description: |
        Get comprehensive user report for a specific period. Includes all streaming metrics, 
        ad performance, revenue, engagement, and session summaries.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: string
            format: uuid
          description: User ID
        - in: query
          name: period
          schema:
            type: string
            enum: [daily, weekly, monthly, yearly]
            default: monthly
          description: Report period
        - in: query
          name: startDate
          schema:
            type: string
            format: date-time
          description: Custom start date (optional, for custom periods)
        - in: query
          name: endDate
          schema:
            type: string
            format: date-time
          description: Custom end date (optional, for custom periods)
      responses:
        '200':
          description: User report retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserReportResponse'
        '404':
          description: User not found
  /api/reports/user/{userId}/whatif:
    post:
      operationId: calculateWhatIfScenario
      summary: Calculate what-if scenario for payout prediction
      description: |
        Calculate projected earnings based on hypothetical metrics. Uses historical averages 
        to predict revenue based on projected views, ad views, minutes, and streams.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: string
            format: uuid
          description: User ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                projectedViews:
                  type: integer
                  description: Projected view count
                projectedAdViews:
                  type: integer
                  description: Projected ad view count
                projectedMinutes:
                  type: number
                  format: double
                  description: Projected streaming minutes
                projectedStreams:
                  type: integer
                  description: Projected stream count
                period:
                  type: string
                  enum: [daily, weekly, monthly, yearly]
                  default: monthly
                  description: Period for projection
      responses:
        '200':
          description: What-if scenario calculated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WhatIfScenarioResponse'
        '404':
          description: User not found
  /api/reports/user/{userId}/export:
    get:
      operationId: exportUserReport
      summary: Export user report (CSV or PDF)
      description: |
        Export user report in CSV or PDF format. Currently returns JSON (CSV/PDF generation to be implemented).
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: string
            format: uuid
          description: User ID
        - in: query
          name: format
          schema:
            type: string
            enum: [csv, pdf]
            default: csv
          description: Export format
        - in: query
          name: period
          schema:
            type: string
            enum: [daily, weekly, monthly, yearly]
            default: monthly
          description: Report period
      responses:
        '200':
          description: Report exported successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserReportResponse'
            text/csv:
              schema:
                type: string
            application/pdf:
              schema:
                type: string
                format: binary
        '404':
          description: User not found
  /api/v1/streams:
    get:
      operationId: getStreams
      summary: List all streams
      security:
        - HMACSignature: []
      responses:
        '200':
          description: List of streams
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/StreamListResponse'
  /api/v1/streams/{id}:
    get:
      operationId: getStreamV1
      summary: Get specific stream details
      security:
        - HMACSignature: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Stream details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StreamDetailResponse'
  /api/v1/streams/{id}/tokens:
    post:
      operationId: mintStreamToken
      summary: Mint stream tokens for access
      security:
        - HMACSignature: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenMintRequest'
      responses:
        '200':
          description: Token minted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'

  # Admin API Endpoints
  /api/admin/settings:
    get:
      operationId: getSystemSettings
      summary: Get system settings (public endpoint)
      description: |
        Public endpoint to retrieve system-wide settings including ad frequency, countdown, and preroll settings.
        This endpoint does not require authentication and is intended for client apps to fetch configuration.
        
        **Note:** Query parameters (offset, limit) are ignored - this endpoint returns settings, not a paginated list.
      tags:
        - Admin
        - Settings
      responses:
        '200':
          description: System settings retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SystemSettingsResponse'
              example:
                general:
                  siteName: "PubFuse"
                  siteDescription: "Live streaming platform for developers"
                  timezone: "UTC"
                  maxSessionsPerUser: 10
                  allowGuestSessions: true
                security:
                  sessionTimeoutMinutes: 60
                  maxLoginAttempts: 5
                  passwordMinLength: 8
                  requireTwoFactor: false
                  enableRateLimiting: true
                  allowedOrigins: ["https://pubfuse.com"]
                notifications:
                  notificationEmail: "admin@pubfuse.com"
                  emailNewUsers: true
                  emailNewSessions: true
                  emailErrors: true
                  inAppAlerts: true
                  inAppSounds: false
                api:
                  apiVersion: "v1"
                  rateLimitPerHour: 1000
                  webhookTimeoutSeconds: 30
                  enableCORS: true
                  enableWebhooks: true
                googleIMA:
                  enabled: false
                  publisherId: null
                  appId: null
                  contentSourceId: null
                  videoId: null
                recording:
                  recordingMethod: "room_composite"
                  recordingFormat: "hls"
                  hostStreamingQuality: "1080p"
                  viewerStreamingQuality: "720p"
                  participantStreamingQuality: "720p"
                  recordingQuality: "1080p"
                  mobileWidth: 390
                  mobileHeight: 644
                  videoCodec: "H264_HIGH"
                  videoBitrate1080p: 5000000
                  videoBitrate720p: 3000000
                  videoFramerate: 30
                  audioCodec: "OPUS"
                  audioBitrate: 128000
                  audioSampleRate: 48000
                  audioChannels: 2
                  layoutStyle: "single-speaker"
                ads:
                  adFrequencyMinutes: 15
                  adCountdownEnabled: true
                  adPrerollEnabled: true
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Failed to retrieve system settings"
  /api/admin/sdk-clients:
    post:
      operationId: createSdkClient
      summary: Create SDK client
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SDKClientRegistrationRequest'
      responses:
        '200':
          description: SDK client created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SDKClientResponse'
  /api/users/signup:
    post:
      operationId: createUserApi
      summary: Create user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserSignupRequest'
      responses:
        '200':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'
  /api/users/login:
    post:
      operationId: loginUserApi
      summary: User login - WORKING ENDPOINT
      description: |
        Authenticates a user with email and password. Returns JWT token and user profile data.
        This endpoint is currently working and tested with bcrypt password verification.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserLoginRequest'
            example:
              email: "tester@pubfuse.com"
              password: "pubfuseguest"
      responses:
        '200':
          description: Login successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserLoginResponse'
              example:
                token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                user:
                  id: "123e4567-e89b-12d3-a456-426614174000"
                  username: "tester"
                  email: "tester@pubfuse.com"
                  displayName: "Test User"
                  firstName: "Test"
                  lastName: "User"
                  profileImageUrl: null
                  isActive: true
                  createdAt: "2024-01-01T00:00:00Z"
                  updatedAt: "2024-01-01T00:00:00Z"
        '401':
          description: Invalid credentials
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Invalid credentials"
  /forgot-password:
    get:
      operationId: forgotPasswordForm
      summary: Get forgot password form
      tags:
        - Authentication
      responses:
        '200':
          description: Forgot password form
          content:
            text/html:
              schema:
                type: string
    post:
      operationId: requestPasswordReset
      summary: Request password reset
      tags:
        - Authentication
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ForgotPasswordRequest'
      responses:
        '200':
          description: Password reset instructions sent (if email exists)
          content:
            text/plain:
              schema:
                type: string
                example: "If the email exists, you will receive password reset instructions."
        '500':
          description: Server error
          content:
            text/plain:
              schema:
                type: string
                example: "Something went wrong."
  /reset-password:
    get:
      operationId: resetPasswordForm
      summary: Get password reset form
      tags:
        - Authentication
      parameters:
        - name: token
          in: query
          required: true
          schema:
            type: string
          description: Password reset token
      responses:
        '200':
          description: Password reset form
          content:
            text/html:
              schema:
                type: string
        '400':
          description: Reset token is required
    post:
      operationId: resetPassword
      summary: Reset password with token
      tags:
        - Authentication
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordRequest'
      responses:
        '200':
          description: Password reset successfully
          content:
            text/plain:
              schema:
                type: string
                example: "Password reset successfully"
        '400':
          description: Bad request (invalid token, passwords don't match, etc.)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Invalid or expired reset token"
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Something went wrong."
  /api/app-ids:
    get:
      operationId: getAppIds
      summary: Get app IDs
      responses:
        '200':
          description: List of app IDs
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AppIdResponse'

  # User Management (JWT Authentication)
  /api/users/profile/full:
    get:
      operationId: getFullProfile
      summary: Get complete user profile with social metrics
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Full user profile with follower/following counts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FullUserProfileResponse'
        '401':
          description: Missing or invalid JWT token
  /api/users/{id}/profile:
    get:
      operationId: getUserProfileById
      summary: Get any user's profile with all stats
      description: |
        Retrieves profile for **any user by ID** including:
        - Basic info (username, email, phone, name)
        - Social stats (followers_count, following_count, streams_count)
        - Account status and verification flags
        
        **Streams count**: Only includes actual streaming sessions (excludes phone and message sessions).
        
        **Note**: Returns user data directly (not wrapped in a response object).
        Uses raw SQL for optimal performance and accuracy.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: User ID to get profile for
      responses:
        '200':
          description: User profile with all stats
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfileResponse'
              example:
                id: "A8ABD9CF-558C-4746-983A-F3B76C7E979B"
                username: "QA 1"
                email: "tester1@pubfuse.com"
                phoneNumber: "6176159681"
                firstName: "Test"
                lastName: "1"
                isActive: true
                emailVerified: false
                phoneVerified: false
                appIdName: "Pubfuse"
                followers_count: 1
                following_count: 1
                streams_count: 86
                connectionCount: 0
                createdAt: "2025-09-26T00:08:00Z"
                updatedAt: "2025-09-26T00:39:29Z"
        '401':
          description: Missing or invalid JWT token
        '404':
          description: User not found
  /api/users/profile:
    get:
      operationId: getCurrentUserProfile
      summary: Get current user's profile with all stats
      description: |
        Retrieves the **authenticated user's** complete profile including:
        - Basic info (username, email, phone, name)
        - Social stats (followers_count, following_count, streams_count)
        - Account status and verification flags
        
        **Streams count**: Only includes actual streaming sessions (excludes phone and message sessions).
        
        **Note**: Returns user data directly (not wrapped in a response object).
        Uses raw SQL for optimal performance and accuracy.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: User profile with all stats
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfileResponse'
              example:
                id: "A8ABD9CF-558C-4746-983A-F3B76C7E979B"
                username: "QA 1"
                email: "tester1@pubfuse.com"
                phoneNumber: "6176159681"
                firstName: "Test"
                lastName: "1"
                isActive: true
                emailVerified: false
                phoneVerified: false
                appIdName: "Pubfuse"
                followers_count: 1
                following_count: 1
                streams_count: 86
                connectionCount: 0
                createdAt: "2025-09-26T00:08:00Z"
                updatedAt: "2025-09-26T00:39:29Z"
        '401':
          description: Missing or invalid JWT token
    put:
      operationId: updateUserProfile
      summary: Update current user's profile
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateUserProfileRequest'
      responses:
        '200':
          description: Profile updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfileResponse'
        '401':
          description: Missing or invalid JWT token
    delete:
      operationId: deleteUserProfile
      summary: Delete the authenticated user's account
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Account deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleSuccessResponse'
        '401':
          description: Missing or invalid JWT token
  /api/users/change-password:
    post:
      operationId: changePassword
      summary: Change the authenticated user's password
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChangePasswordRequest'
      responses:
        '200':
          description: Password changed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleSuccessResponse'
        '400':
          description: Invalid current password or validation error
        '401':
          description: Missing or invalid JWT token
  /api/users/follow/{id}:
    post:
      operationId: followUser
      summary: Follow a user by ID (idempotent - safe to call multiple times)
      description: |
        Creates a follow relationship between the authenticated user and the target user.
        - **Idempotent:** Returns success even if already following (returns existing relationship)
        - **Validation:** Prevents self-follows
        - **Implementation:** Uses raw SQL for reliability
        - **Status:** ✅ WORKING (fixed Oct 17, 2025)
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: UUID of the user to follow
      responses:
        '200':
          description: Followed successfully (or already following)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserFollowResponse'
        '400':
          description: Invalid request (e.g., trying to follow yourself, user not found)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                  reason:
                    type: string
                    enum:
                      - "You cannot follow yourself"
                      - "User not found or not available to follow"
                      - "Invalid user ID"
        '401':
          description: Missing or invalid JWT token
    delete:
      operationId: unfollowUser
      summary: Unfollow a user by ID (idempotent - safe to call multiple times)
      description: |
        Removes a follow relationship between the authenticated user and the target user.
        - **Idempotent:** Returns success even if not following (DELETE is naturally idempotent)
        - **Validation:** Prevents self-unfollows
        - **Implementation:** Uses raw SQL for reliability
        - **Status:** ✅ WORKING (fixed Oct 17, 2025)
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: UUID of the user to unfollow
      responses:
        '200':
          description: Unfollowed successfully (or was not following)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleSuccessResponse'
        '400':
          description: Invalid request (e.g., trying to unfollow yourself)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                  reason:
                    type: string
                    enum:
                      - "You cannot unfollow yourself"
                      - "Invalid user ID"
        '401':
          description: Missing or invalid JWT token
  /api/users/followers/count:
    get:
      operationId: getFollowersCount
      summary: Get follower count for authenticated user
      description: |
        Returns the count of users following the authenticated user.
        Uses raw SQL for optimal performance.
        Can be used as a fallback if GET /api/users/profile fails.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Follower count
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FollowCountResponse'
              example:
                count: 1
        '401':
          description: Missing or invalid JWT token
  /api/users/following/count:
    get:
      operationId: getFollowingCount
      summary: Get following count for authenticated user
      description: |
        Returns the count of users the authenticated user is following.
        Uses raw SQL for optimal performance.
        Can be used as a fallback if GET /api/users/profile fails.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Following count
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FollowCountResponse'
              example:
                count: 1
        '401':
          description: Missing or invalid JWT token
  /api/users/search:
    get:
      operationId: searchUsers
      summary: Search for users by username, email, or name
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: q
          required: true
          schema:
            type: string
          description: Search query
        - in: query
          name: limit
          schema:
            type: integer
            default: 20
          description: Maximum number of results
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserSearchResponse'
        '401':
          description: Missing or invalid JWT token
  /api/users/blocks:
    get:
      operationId: getBlockedUsers
      summary: List blocked users
      description: |
        Returns the list of users that the authenticated account has blocked. Blocked users cannot follow, message, or interact with the caller.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Blocked users retrieved
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BlockedUserResponse'
        '401':
          description: Missing or invalid JWT token
  /api/users/blocks/{id}:
    post:
      operationId: blockUser
      summary: Block a user
      description: |
        Blocks the specified user and removes any existing follows or connections.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: ID of the user to block
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BlockUserRequest'
      responses:
        '200':
          description: User blocked successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserBanResponse'
        '400':
          description: Invalid user ID
        '401':
          description: Missing or invalid JWT token
    delete:
      operationId: unblockUser
      summary: Unblock a user
      description: Removes a previously blocked user.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: User unblocked successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserBanResponse'
        '401':
          description: Missing or invalid JWT token
        '404':
          description: Block not found
  /api/users/account:
    delete:
      operationId: deleteAccount
      summary: Delete the authenticated account
      description: |
        Permanently deletes the authenticated user's account and associated data. This action cannot be undone.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Account deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteAccountResponse'
        '401':
          description: Missing or invalid JWT token
  /api/users/music/authenticate:
    post:
      operationId: authenticateMusicUser
      summary: Authenticate with pubfu.se and create/update link
      description: |
        Authenticates a pubfuse.com user with pubfu.se music platform and creates/updates the user link.
        This enables single sign-on between pubfuse.com and pubfu.se, allowing users to manage music creator profiles.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Link created/updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MusicUserLinkResponse'
        '400':
          description: Failed to authenticate with music server
        '401':
          description: Missing or invalid JWT token
  /api/users/music/link:
    get:
      operationId: getMusicUserLink
      summary: Get current user's music link status
      description: Returns the music user link for the authenticated pubfuse.com user.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Link found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MusicUserLinkResponse'
        '404':
          description: No music user link found
        '401':
          description: Missing or invalid JWT token
    post:
      operationId: createOrUpdateMusicUserLink
      summary: Create or update music user link
      description: Creates or updates a link between the authenticated pubfuse.com user and pubfu.se music user.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Link created/updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MusicUserLinkResponse'
        '401':
          description: Missing or invalid JWT token
    put:
      operationId: updateMusicUserLink
      summary: Update music user link metadata
      description: Updates metadata for the authenticated user's music link.
      security:
        - BearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                isActive:
                  type: boolean
                  description: Whether the link is active
                metadata:
                  type: object
                  additionalProperties:
                    type: string
                  description: Additional metadata about the link
      responses:
        '200':
          description: Link updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MusicUserLinkResponse'
        '404':
          description: No music user link found
        '401':
          description: Missing or invalid JWT token
    delete:
      operationId: deleteMusicUserLink
      summary: Delete music user link
      description: Deletes the music user link for the authenticated user.
      security:
        - BearerAuth: []
      responses:
        '204':
          description: Link deleted successfully
        '401':
          description: Missing or invalid JWT token
  /api/users/music/refresh-token:
    post:
      operationId: refreshMusicToken
      summary: Refresh pubfu.se token
      description: Refreshes the pubfu.se authentication token if it's expired or about to expire.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Token refreshed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MusicUserLinkResponse'
        '404':
          description: No music user link found
        '401':
          description: Missing or invalid JWT token
  /api/users/music/search:
    get:
      operationId: searchMusicUsers
      summary: Search for existing pubfu.se users
      description: |
        Searches for existing pubfu.se users by email or nickname.
        This endpoint allows users to find existing pubfu.se accounts to link to their pubfuse.com account.
        
        **Authorization:**
        - If the search result email matches the current user's email, linking can proceed without password
        - If emails don't match, password verification will be required when linking
        
        **Use Case:**
        Use this endpoint to allow users to search for and link to existing pubfu.se accounts instead of creating new ones.
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: q
          required: true
          schema:
            type: string
          description: Search query (email or nickname)
          example: "user@example.com"
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MusicUserSearchResult'
        '400':
          description: Missing search query parameter
        '401':
          description: Missing or invalid JWT token
  /api/users/music/link-existing:
    post:
      operationId: linkToExistingMusicUser
      summary: Link to an existing pubfu.se user
      description: |
        Links the authenticated pubfuse.com user to an existing pubfu.se user account.
        
        **Authorization Requirements:**
        - **Email Match:** If the pubfu.se user's email matches the pubfuse.com user's email, linking proceeds automatically (no password required)
        - **Email Mismatch:** If emails don't match, the `password` field is required to verify ownership of the pubfu.se account
        
        **Security:**
        - Password verification is performed by calling pubfu.se's `/gettoken/:email/:pass/` endpoint
        - The endpoint returns a JWT token if the password is correct, or an error if incorrect
        - If password verification fails (no token returned), the request is rejected with a 401 Unauthorized error
        - The `musicUserEmail` field can be provided from search results to avoid an extra API call
        
        **Use Case:**
        This endpoint enables users to link their pubfuse.com account to an existing pubfu.se account they already own,
        rather than creating a new account. This is useful when users have separate accounts on both platforms.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkExistingMusicUserRequest'
      responses:
        '200':
          description: Link created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MusicUserLinkResponse'
        '400':
          description: Invalid request (missing musicUserId)
        '401':
          description: |
            Unauthorized - Either:
            - Missing or invalid JWT token
            - Email mismatch and password not provided
            - Email mismatch and password verification failed
        '404':
          description: Target pubfu.se user not found
  /api/users/music/proxy/{path}:
    get:
      operationId: proxyMusicApiGet
      summary: Proxy GET request to pubfu.se API
      description: |
        Proxies any GET request to pubfu.se API. The path parameter should be the full pubfu.se API path.
        Example: `/api/users/music/proxy/api/v1/music/artist/123` will call `pubfu.se/api/v1/music/artist/123`
        
        **Headers sent to pubfu.se:** The proxy automatically sends the headers pubfu.se expects:
        - `Content-Type: application/json` (or the value you send; defaults to application/json)
        - `Authorization: Bearer <music-token>` (resolved from your pubfuse.com JWT and music link).
        **CORS:** pubfu.se allows CORS from www.pubfuse.com, so proxy and cross-origin requests succeed.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: path
          required: true
          schema:
            type: string
          description: The pubfu.se API path (e.g., "api/v1/music/artist/123")
      responses:
        '200':
          description: Response from pubfu.se API
        '404':
          description: No music user link found
        '401':
          description: Missing or invalid JWT token
    post:
      operationId: proxyMusicApiPost
      summary: Proxy POST request to pubfu.se API
      description: |
        Proxies any POST request to pubfu.se API. The path parameter should be the full pubfu.se API path.
        Example: `/api/users/music/proxy/api/v1/music/album` will call `pubfu.se/api/v1/music/album`
        Send `Content-Type: application/json` and body; proxy forwards them and adds `Authorization: Bearer <music-token>` to pubfu.se. CORS is enabled on pubfu.se for pubfuse.com.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: path
          required: true
          schema:
            type: string
          description: The pubfu.se API path (e.g., "api/v1/music/album")
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              description: Request body to forward to pubfu.se
      responses:
        '200':
          description: Response from pubfu.se API
        '404':
          description: No music user link found
        '401':
          description: Missing or invalid JWT token
    put:
      operationId: proxyMusicApiPut
      summary: Proxy PUT request to pubfu.se API
      description: Proxies any PUT request to pubfu.se API.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: path
          required: true
          schema:
            type: string
          description: The pubfu.se API path
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Response from pubfu.se API
        '404':
          description: No music user link found
        '401':
          description: Missing or invalid JWT token
    delete:
      operationId: proxyMusicApiDelete
      summary: Proxy DELETE request to pubfu.se API
      description: Proxies any DELETE request to pubfu.se API.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: path
          required: true
          schema:
            type: string
          description: The pubfu.se API path
      responses:
        '200':
          description: Response from pubfu.se API
        '404':
          description: No music user link found
        '401':
          description: Missing or invalid JWT token

  # ---------------------------------------------------------------------------
  # Music proxy - PubfuseNebula search endpoints (tracks, users, projects, etc.)
  # Direct URLs: https://www.pubfu.se/search/... ; use via proxy with token.
  # ---------------------------------------------------------------------------
  /api/users/music/proxy/search/all:
    get:
      operationId: proxySearchAll
      summary: Search all (pubfu.se)
      description: Proxies to pubfu.se search across all types. Direct URL `GET https://www.pubfu.se/search/all/`. Requires user auth and music link; token sent to pubfu.se.
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/all/contains/{searchText}:
    get:
      operationId: proxySearchAllContains
      summary: Search all by text (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/all/contains/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/tracks/contains/{searchText}:
    get:
      operationId: proxySearchTracksContains
      summary: Search tracks by text (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/tracks/contains/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/lyrics/contains/{searchText}:
    get:
      operationId: proxySearchLyricsContains
      summary: Search lyrics by text (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/lyrics/contains/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/projects/contains/{searchText}:
    get:
      operationId: proxySearchProjectsContains
      summary: Search projects by text (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/projects/contains/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/creators/contains/{searchText}:
    get:
      operationId: proxySearchCreatorsContains
      summary: Search creators by text (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/creators/contains/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/users/nickname/{searchText}:
    get:
      operationId: proxySearchUsersNickname
      summary: Search users by nickname (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/users/nickname/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/users/lastname/{searchText}:
    get:
      operationId: proxySearchUsersLastname
      summary: Search users by last name (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/users/lastname/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/users/firstname/lastname/{searchText}:
    get:
      operationId: proxySearchUsersFirstnameLastname
      summary: Search users by firstname lastname (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/users/firstname/lastname/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/users/vibe/{searchText}:
    get:
      operationId: proxySearchUsersVibe
      summary: Search users by vibe (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/users/vibe/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/users/frequency/{searchText}:
    get:
      operationId: proxySearchUsersFrequency
      summary: Search users by frequency (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/users/frequency/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/events/contains/{searchText}:
    get:
      operationId: proxySearchEventsContains
      summary: Search events by text (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/events/contains/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/events/venue/{searchText}:
    get:
      operationId: proxySearchEventsVenue
      summary: Search events by venue (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/events/venue/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/posts/title/{searchText}:
    get:
      operationId: proxySearchPostsTitle
      summary: Search posts by title (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/posts/title/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/posts/user/{searchText}:
    get:
      operationId: proxySearchPostsUser
      summary: Search posts by user (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/posts/user/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/user/tag/{searchText}:
    get:
      operationId: proxySearchUserTag
      summary: Search user by tag (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/user/tag/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/project/tag/{searchText}:
    get:
      operationId: proxySearchProjectTag
      summary: Search project by tag (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/project/tag/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/track/tag/{searchText}:
    get:
      operationId: proxySearchTrackTag
      summary: Search track by tag (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/track/tag/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found
  /api/users/music/proxy/search/artist/tag/{searchText}:
    get:
      operationId: proxySearchArtistTag
      summary: Search artist by tag (pubfu.se)
      description: Proxies to `GET https://www.pubfu.se/search/artist/tag/{search_text}/`. Requires user auth and music link.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: searchText
          required: true
          schema: { type: string }
        - in: query
          name: page
          schema: { type: integer, default: 1 }
        - in: query
          name: per
          schema: { type: integer, default: 5 }
      responses:
        '200':
          description: Response from pubfu.se
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No music user link found

  /api/moderation/reports:
    post:
      operationId: createContentReport
      summary: Report objectionable content
      description: |
        Creates a moderation report for objectionable content or abusive users. Reports are reviewed by Pubfuse moderators.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContentReportRequest'
      responses:
        '201':
          description: Report submitted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContentReportResponse'
        '400':
          description: Invalid request data
        '401':
          description: Missing or invalid JWT token
    get:
      operationId: listContentReports
      summary: List submitted content reports
      description: Returns moderation reports previously submitted by the authenticated user.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Reports retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContentReportResponse'
        '401':
          description: Missing or invalid JWT token


  # Contacts & Sync API
  /api/contacts/sync:
    post:
      operationId: syncContacts
      summary: Sync device contacts and discover Pubfuse users (creates sync tracking records)
      description: |
        Syncs device contacts with the server and identifies which contacts are Pubfuse users.
        - **Validation:** Only marks users as isPubfuseUser if they exist in the users table
        - **Matching:** Matches by normalized phone numbers
        - **Follow Status:** Includes isFollowing and isFollowedBy for each contact
        - **Status:** ✅ WORKING (enhanced validation Oct 17, 2025)
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactsSyncRequest'
      responses:
        '200':
          description: Contacts synced successfully and sync tracking records created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactsSyncResponse'
        '401':
          description: Missing or invalid JWT token
        '400':
          description: Invalid request data
  /api/contacts:
    get:
      operationId: getContacts
      summary: Get user's synced contacts
      security:
        - BearerAuth: []
      responses:
        '200':
          description: User's contacts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContactResponse'
        '401':
          description: Missing or invalid JWT token
  /api/contacts/synced:
    get:
      operationId: getSyncedContacts
      summary: Get contacts with sync status
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Synced contacts with status
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SyncedContactResponse'
        '401':
          description: Missing or invalid JWT token
  /api/contacts/{id}:
    get:
      operationId: getContact
      summary: Get specific contact details
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Contact details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactResponse'
        '401':
          description: Missing or invalid JWT token
        '404':
          description: Contact not found
    put:
      operationId: updateContact
      summary: Update contact information
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactUpdateRequest'
      responses:
        '200':
          description: Contact updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactResponse'
        '401':
          description: Missing or invalid JWT token
        '404':
          description: Contact not found
    delete:
      operationId: deleteContact
      summary: Delete contact
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Contact deleted
        '401':
          description: Missing or invalid JWT token
        '404':
          description: Contact not found
  /api/contacts/{id}/follow:
    post:
      operationId: followContact
      summary: Follow a contact who is on Pubfuse
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Contact followed successfully
        '401':
          description: Missing or invalid JWT token
        '400':
          description: Contact is not a Pubfuse user
        '409':
          description: Already following this user
    delete:
      operationId: unfollowContact
      summary: Unfollow a contact
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Contact unfollowed successfully
        '401':
          description: Missing or invalid JWT token
        '404':
          description: Not currently following this user
  /api/contacts/{id}/sync:
    post:
      operationId: syncContact
      summary: Manually sync a specific contact
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Contact synced
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactSyncStatusResponse'
        '401':
          description: Missing or invalid JWT token
        '404':
          description: Contact not found
  /api/contacts/{id}/sync-status:
    get:
      operationId: getContactSyncStatus
      summary: Get sync status for a specific contact
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Contact sync status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactSyncStatusResponse'
        '401':
          description: Missing or invalid JWT token
        '404':
          description: Contact not found

  # File Management API
  /api/files:
    post:
      operationId: createFile
      summary: Upload/create a new file
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FileCreateRequest'
      responses:
        '200':
          description: File created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
        '400':
          description: Invalid request data
    get:
      operationId: getFiles
      summary: Get all files for the authenticated user
      security:
        - BearerAuth: []
      responses:
        '200':
          description: List of files
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
  /api/files/upload:
    post:
      operationId: uploadFile
      summary: Upload a file using multipart/form-data
      description: |
        Upload a file with automatic image processing. The server will automatically:
        - Generate multiple image sizes (thumbnail, small, medium, large, original)
        - Store files in user-specific directories
        - Support associating files with broadcasts, messages, calls, or contacts
        
        **Supported file types**: Images (JPEG, PNG)
        **Max file size**: 20MB
        
        **Multipart form fields**:
        - `file` (required): The file data
        - `filename` (optional): Custom filename
        - `description` (optional): File description
        - `categoryName` (optional): Category (e.g., "profile", "broadcast", "poster")
        - `tags` (optional): Comma-separated tags (e.g., "poster,broadcast", "profilepic")
        - `mimeType` (optional): MIME type (defaults based on file extension)
        - `forBroadcastId` (optional): UUID of broadcast to associate file with
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                filename:
                  type: string
                description:
                  type: string
                categoryName:
                  type: string
                  example: "broadcast"
                tags:
                  type: string
                  example: "poster,broadcast"
                mimeType:
                  type: string
                  example: "image/jpeg"
                forBroadcastId:
                  type: string
                  format: uuid
      responses:
        '200':
          description: File uploaded successfully with all image sizes generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
        '400':
          description: Invalid request data
        '413':
          description: Payload too large (max 20MB)
  /api/files/{id}:
    get:
      operationId: getFile
      summary: Get specific file details
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: File details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
        '404':
          description: File not found
    put:
      operationId: updateFile
      summary: Update file information
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FileUpdateRequest'
      responses:
        '200':
          description: File updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
        '404':
          description: File not found
    delete:
      operationId: deleteFile
      summary: Delete file
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: File deleted
        '401':
          description: Missing or invalid JWT token
        '404':
          description: File not found
  /api/files/{id}/public:
    get:
      operationId: getPublicFile
      summary: Public file access (for LiveKit and external services)
      description: |
        Public endpoint to access files without authentication. This endpoint is designed for:
        - LiveKit Egress/Ingress services that need to access media files
        - External services that cannot provide authentication tokens
        - Public file sharing scenarios
        
        **Note:** This endpoint allows public access to files. Use with caution and ensure files
        are appropriate for public access. For authenticated access, use `/api/files/{id}/download`.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: File ID
        - in: query
          name: token
          schema:
            type: string
          description: Optional token for additional security (future use)
      responses:
        '200':
          description: File content
          headers:
            Content-Type:
              schema:
                type: string
              description: MIME type of the file
            Content-Disposition:
              schema:
                type: string
              description: File disposition header
            Cache-Control:
              schema:
                type: string
              description: Cache control header (public, max-age=3600)
            Access-Control-Allow-Origin:
              schema:
                type: string
              description: CORS header (allows all origins)
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '404':
          description: File not found or missing on server
        '500':
          description: Server error
  /api/files/{id}/download:
    get:
      operationId: downloadFile
      summary: Download file (authenticated)
      description: |
        Download a file with authentication. Requires JWT token.
        This endpoint verifies that the user has permission to access the file.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: File ID
      responses:
        '200':
          description: File content
          headers:
            Content-Type:
              schema:
                type: string
            Content-Disposition:
              schema:
                type: string
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '401':
          description: Missing or invalid JWT token
        '403':
          description: Not authorized to access this file
        '404':
          description: File not found
  /api/files/foruser/{userId}:
    get:
      operationId: getFilesForUser
      summary: Get files for a specific user with optional filters
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: string
            format: uuid
        - in: query
          name: category
          schema:
            type: string
          description: Filter by category (e.g., profile, mp3, etc.)
        - in: query
          name: tag
          schema:
            type: string
          description: Filter by tag (e.g., profilepic)
      responses:
        '200':
          description: List of files
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
  /api/files/profileimage/foruser/{userId}:
    get:
      operationId: getProfileImageForUser
      summary: Get profile image for a user
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Profile image file
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
        '404':
          description: Profile image not found
  /api/files/contactimage/foruser/{userId}:
    get:
      operationId: getContactImageForUser
      summary: Get contact image for a user
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Contact image file
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
        '404':
          description: Contact image not found
  /api/files/forcontact/{contactId}:
    get:
      operationId: getFilesForContact
      summary: Get files associated with a contact
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: contactId
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: List of files
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
  /api/files/formessage/{messageId}:
    get:
      operationId: getFilesForMessage
      summary: Get files associated with a message
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: messageId
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: List of files
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
  /api/files/forcall/{callId}:
    get:
      operationId: getFilesForCall
      summary: Get files associated with a call
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: callId
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: List of files
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
  /api/files/forbroadcast/{broadcastId}:
    get:
      operationId: getFilesForBroadcast
      summary: Get files associated with a broadcast
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: broadcastId
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: List of files
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FileResponse'
        '401':
          description: Missing or invalid JWT token
  /api/files/waveform/for/{forId}:
    get:
      operationId: getWaveformForObject
      summary: Get waveform file for an object
      description: |
        Get the waveform image file associated with an object (e.g., music track, note).
        Waveforms are automatically generated for audio files when uploaded and stored
        in the files table with a `for_id` pointing to the related object.
        
        **Use cases**:
        - Display waveform as background in music track lists
        - Show waveform visualization for audio notes
        - Provide visual feedback for audio content
        
        **Waveform generation**: Waveforms are automatically generated when audio files
        are uploaded. If a waveform doesn't exist, this endpoint will return 404.
        Use the admin endpoint `/api/pubfuse-admin/waveforms/generate-for-track/{trackId}`
        to manually generate waveforms for existing tracks.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: forId
          required: true
          schema:
            type: string
            format: uuid
          description: The ID of the object (track ID, note ID, etc.) to get the waveform for
      responses:
        '200':
          description: Waveform file found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileResponse'
        '404':
          description: Waveform not found for the specified object
        '401':
          description: Missing or invalid JWT token
  /api/files/broadcastposter/forbroadcast/{broadcastId}:
    get:
      operationId: getBroadcastPosterImageForBroadcast
      summary: Get broadcast poster image
      description: |
        Get the poster image for a broadcast session. Returns the most recently uploaded poster image
        associated with the broadcast. Poster images are identified by category "broadcast" or "poster"
        or by having the "poster" tag.
        
        **Use cases**:
        - Display poster thumbnail in broadcast lists
        - Show preview image before joining a broadcast
        - Replace placeholder images in UI
        
        **Image sizes**: The response includes paths to multiple sizes (thumbnail, small, medium, large, original).
        Use the smallest appropriate size for list views (thumbnail or small) for faster loading.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: broadcastId
          required: true
          schema:
            type: string
            format: uuid
          description: The broadcast session ID
      responses:
        '200':
          description: Broadcast poster image found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileResponse'
              example:
                id: "550e8400-e29b-41d4-a716-446655440000"
                userId: "660e8400-e29b-41d4-a716-446655440001"
                name: "broadcast-poster-abc123.jpg"
                mimeType: "image/jpeg"
                filePathOriginal: "userfiles/660e8400-e29b-41d4-a716-446655440001/broadcast-poster-abc123-original.jpg"
                filePathThumbnail: "userfiles/660e8400-e29b-41d4-a716-446655440001/broadcast-poster-abc123-thumbnail.jpg"
                filePathSmall: "userfiles/660e8400-e29b-41d4-a716-446655440001/broadcast-poster-abc123-small.jpg"
                filePathMedium: "userfiles/660e8400-e29b-41d4-a716-446655440001/broadcast-poster-abc123-medium.jpg"
                filePathLarge: "userfiles/660e8400-e29b-41d4-a716-446655440001/broadcast-poster-abc123-large.jpg"
                categoryName: "broadcast"
                tags: ["poster", "broadcast"]
                forBroadcastId: "770e8400-e29b-41d4-a716-446655440002"
        '401':
          description: Missing or invalid JWT token
        '404':
          description: No poster image found for this broadcast

  # Contact History API (Calls & Messages)
  /api/contact-history:
    post:
      operationId: createCallOrMessage
      summary: Create a call or message in contact history
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateContactHistoryRequest'
      responses:
        '200':
          description: Call or message created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
        '400':
          description: Invalid request data
    get:
      operationId: getAllContactHistory
      summary: Get all contact history records
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Contact history records
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
  /api/contact-history/{id}:
    get:
      operationId: getContactHistoryById
      summary: Get specific contact history record
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Contact history record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
        '404':
          description: Contact history not found
    put:
      operationId: updateCallOrMessage
      summary: Update a call or message
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateContactHistoryRequest'
      responses:
        '200':
          description: Call or message updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
        '404':
          description: Contact history not found
    delete:
      operationId: deleteContactHistory
      summary: Delete a contact history record
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Contact history deleted successfully
        '401':
          description: Authentication required
        '404':
          description: Contact history not found
  /api/contact-history/{id}/join:
    post:
      operationId: joinCallOrMessage
      summary: Add a user to a call or message
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JoinContactHistoryRequest'
      responses:
        '200':
          description: User added to call or message
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
        '404':
          description: Contact history not found
  /api/contact-history/{id}/hold:
    post:
      operationId: holdCall
      summary: Hold or unhold a call
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HoldCallRequest'
      responses:
        '200':
          description: Call hold status updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
        '404':
          description: Contact history not found
        '400':
          description: Invalid operation (only calls can be held)
  /api/contact-history/{id}/transfer:
    post:
      operationId: transferCall
      summary: Transfer a call to another host
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TransferCallRequest'
      responses:
        '200':
          description: Call transferred successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
        '404':
          description: Contact history not found
        '400':
          description: Invalid operation (only calls can be transferred)
  /api/contact-history/calls:
    get:
      operationId: getCalls
      summary: Get all call records
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Call records
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
  /api/contact-history/messages:
    get:
      operationId: getMessages
      summary: Get all message records
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Message records
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
  /api/contact-history/user/{userId}:
    get:
      operationId: getUserContactHistory
      summary: Get contact history for a specific user
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: string
            format: uuid
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: User's contact history
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
        '400':
          description: Invalid user ID
  /api/contact-history/notes/{userId}:
    get:
      operationId: getNotes
      summary: Get notes for a specific user
      description: Returns all notes (contact history records where is_note is true) for the specified user
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: string
            format: uuid
          description: User ID to get notes for
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
          description: Maximum number of notes to return
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
          description: Number of notes to skip
      responses:
        '200':
          description: List of notes for the user
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
        '400':
          description: Invalid user ID

  /api/interactions:
    post:
      operationId: createInteraction
      summary: Create a new user interaction (like, gift, spark, etc.)
      description: |
        Creates a new interaction between a user and a target entity.
        Supports various interaction types (like, gift, spark, diamond, token, reaction, etc.)
        and various target types (broadcast_session, project, track, user, post, etc.).
        Uses raw SQL for database operations with proper null checking.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserInteractionRequest'
      responses:
        '200':
          description: Interaction created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserInteractionResponse'
        '400':
          description: Invalid request parameters
        '401':
          description: Authentication required
        '409':
          description: Interaction already exists (idempotent - returns existing interaction)

  /api/interactions/{id}:
    delete:
      operationId: deleteInteraction
      summary: Delete an interaction (e.g., unlike)
      description: |
        Deletes a user interaction. The user must own the interaction.
        Uses raw SQL for database operations with proper null checking.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Interaction ID
      responses:
        '200':
          description: Interaction deleted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleSuccessResponse'
        '401':
          description: Authentication required
        '403':
          description: Cannot delete another user's interaction
        '404':
          description: Interaction not found

  /api/interactions/target/{targetType}/{targetId}:
    get:
      operationId: getInteractionsForTarget
      summary: Get interactions for a specific target
      description: |
        Returns all interactions for a specific target entity (e.g., all likes for a project).
        Uses raw SQL for database operations with proper null checking.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: targetType
          required: true
          schema:
            type: string
            enum: [broadcast_session, project, track, user, post, comment, event, playlist, creator, message, note, collectable]
          description: Type of target entity
        - in: path
          name: targetId
          required: true
          schema:
            type: string
          description: ID of the target entity
        - in: query
          name: interactionType
          schema:
            type: string
            enum: [like, gift, spark, diamond, token, reaction, tip, boost, favorite, share, view, comment, follow, subscribe]
          description: Filter by interaction type (optional)
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
          description: Maximum number of interactions to return
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
          description: Number of interactions to skip
      responses:
        '200':
          description: List of interactions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserInteractionResponse'
        '400':
          description: Invalid target type or ID
        '401':
          description: Authentication required

  /api/interactions/user/{userId}:
    get:
      operationId: getInteractionsByUser
      summary: Get interactions by a specific user
      description: |
        Returns all interactions performed by a specific user.
        Uses raw SQL for database operations with proper null checking.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: string
            format: uuid
          description: User ID
        - in: query
          name: interactionType
          schema:
            type: string
            enum: [like, gift, spark, diamond, token, reaction, tip, boost, favorite, share, view, comment, follow, subscribe]
          description: Filter by interaction type (optional)
        - in: query
          name: targetType
          schema:
            type: string
            enum: [broadcast_session, project, track, user, post, comment, event, playlist, creator, message, note, collectable]
          description: Filter by target type (optional)
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
          description: Maximum number of interactions to return
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
          description: Number of interactions to skip
      responses:
        '200':
          description: List of interactions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UserInteractionResponse'
        '400':
          description: Invalid user ID
        '401':
          description: Authentication required

  /api/interactions/stats/{targetType}/{targetId}:
    get:
      operationId: getInteractionStats
      summary: Get interaction statistics for a target
      description: |
        Returns aggregated statistics (counts by type) for all interactions on a target entity.
        Uses raw SQL for database operations with proper null checking.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: targetType
          required: true
          schema:
            type: string
            enum: [broadcast_session, project, track, user, post, comment, event, playlist, creator, message, note, collectable]
          description: Type of target entity
        - in: path
          name: targetId
          required: true
          schema:
            type: string
          description: ID of the target entity
      responses:
        '200':
          description: Interaction statistics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InteractionStatsResponse'
        '400':
          description: Invalid target type or ID
        '401':
          description: Authentication required

  /api/interactions/check/{targetType}/{targetId}:
    get:
      operationId: checkUserInteraction
      summary: Check if current user has interacted with a target
      description: |
        Checks if the authenticated user has performed a specific interaction type with a target.
        Uses raw SQL for database operations with proper null checking.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: targetType
          required: true
          schema:
            type: string
            enum: [broadcast_session, project, track, user, post, comment, event, playlist, creator, message, note, collectable]
          description: Type of target entity
        - in: path
          name: targetId
          required: true
          schema:
            type: string
          description: ID of the target entity
        - in: query
          name: interactionType
          schema:
            type: string
            enum:
              - like
              - gift
              - spark
              - diamond
              - token
              - reaction
              - tip
              - boost
              - favorite
              - share
              - view
              - comment
              - follow
              - subscribe
            default: like
          description: "Interaction type to check (default: like)"
      responses:
        '200':
          description: Interaction check result. Returns the interaction object if found, or null if not found.
          content:
            application/json:
              schema:
                nullable: true
                $ref: '#/components/schemas/UserInteractionResponse'
              examples:
                found:
                  summary: Interaction found
                  value:
                    id: "123e4567-e89b-12d3-a456-426614174000"
                    userId: "123e4567-e89b-12d3-a456-426614174001"
                    interactionType: "like"
                    targetType: "user"
                    targetId: "7C0182D3-3BC1-468A-9D01-C7DEF1A788EE"
                    amount: null
                    currency: null
                    usdValue: null
                    metadata: null
                    description: null
                    createdAt: "2026-02-05T03:00:00Z"
                    updatedAt: "2026-02-05T03:00:00Z"
                notFound:
                  summary: Interaction not found
                  value: null
        '400':
          description: Invalid target type, ID, or interaction type
        '401':
          description: Authentication required

  /api/music/helpers/projects/{id}:
    get:
      operationId: getPufuseInfoForMusicProject
      summary: Get combined pubfu.se + pubfuse.com info for a music project
      description: |
        Returns the linked pubfuse.com user and interaction statistics for a music project,
        along with the owning pubfu.se music user ID (created_by on the underlying creator).
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
          description: Music project ID from pubfu.se
      responses:
        '200':
          description: Combined project info
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PubfuseMusicItemInfoResponse'
        '400':
          description: Invalid project ID
        '401':
          description: Authentication required
        '404':
          description: Project not found in pubfu.se
        '412':
          description: No active music user link exists for the authenticated user

  /api/music/helpers/artists/{id}:
    get:
      operationId: getPufuseInfoForPubfuseArtist
      summary: Get combined pubfu.se + pubfuse.com info for a music artist/creator
      description: |
        Returns the linked pubfuse.com user and interaction statistics for a pubfu.se artist/creator,
        along with the owning pubfu.se music user ID (created_by).
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
          description: Music artist/creator ID from pubfu.se
      responses:
        '200':
          description: Combined artist/creator info
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PubfuseMusicItemInfoResponse'
        '400':
          description: Invalid artist/creator ID
        '401':
          description: Authentication required
        '404':
          description: Artist/creator not found in pubfu.se
        '412':
          description: No active music user link exists for the authenticated user

  /api/music/helpers/creators/{id}:
    get:
      operationId: getPufuseInfoForCreator
      summary: Alias for getPufuseInfoForPubfuseArtist using a creator path
      description: >
        Convenience alias that returns the same payload as
        /api/music/helpers/artists/{id}/pubfuse-info but addressed by "creators" in the path.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
          description: Music creator ID from pubfu.se
      responses:
        '200':
          description: Combined creator info
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PubfuseMusicItemInfoResponse'
        '400':
          description: Invalid creator ID
        '401':
          description: Authentication required
        '404':
          description: Creator not found in pubfu.se
        '412':
          description: No active music user link exists for the authenticated user

  /api/music/helpers/tracks/{id}:
    get:
      operationId: getPufuseInfoForPubfuseTrack
      summary: Get combined pubfu.se + pubfuse.com info for a music track
      description: |
        Returns the linked pubfuse.com user and interaction statistics for a pubfu.se track.
        Also includes the parent project/album object when available.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
          description: Music track ID from pubfu.se
      responses:
        '200':
          description: Combined track info
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PubfuseMusicItemInfoResponse'
        '400':
          description: Invalid track ID
        '401':
          description: Authentication required
        '404':
          description: Track not found in pubfu.se
        '412':
          description: No active music user link exists for the authenticated user

  /api/music/helpers/track/{id}:
    get:
      operationId: getPufuseInfoForTrack
      summary: Alias for getPufuseInfoForPubfuseTrack using a singular path
      description: >
        Convenience alias that returns the same payload as
        /api/music/helpers/tracks/{id}/pubfuse-info but addressed by "track" in the path.
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
          description: Music track ID from pubfu.se
      responses:
        '200':
          description: Combined track info
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PubfuseMusicItemInfoResponse'
        '400':
          description: Invalid track ID
        '401':
          description: Authentication required
        '404':
          description: Track not found in pubfu.se
        '412':
          description: No active music user link exists for the authenticated user

  /api/drops:
    get:
      operationId: getDrops
      summary: Get latest drops (Music Projects, Tracks, and Broadcast Sessions)
      description: |
        Returns latest drops organized by date.
        - If userId query parameter is provided: Returns latest Music Projects, Music Tracks for their linked musicID, along with their latest live Stream broadcastSessions (active, completed, created) organized by date
        - If no userId: Returns top artists results organized by date
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: userId
          schema:
            type: string
            format: uuid
          description: Optional user ID to get drops for specific user
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
          description: Maximum number of items to return
      responses:
        '200':
          description: Drops response organized by date
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DropsResponse'
        '401':
          description: Authentication required

  # Playlists (JWT)
  /api/playlists:
    get:
      operationId: getPlaylists
      summary: List playlists
      description: List playlists for a user. If userId query is omitted and authenticated, returns current user's playlists. Visibility (public/private/show_followers_only) is enforced.
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: userId
          schema:
            type: string
            format: uuid
          description: User ID whose playlists to list (optional when authenticated)
      responses:
        '200':
          description: List of playlists
          content:
            application/json:
              schema:
                type: object
                properties:
                  playlists:
                    type: array
                    items:
                      $ref: '#/components/schemas/PlaylistListItem'
        '401':
          description: Authentication required when userId not provided
    post:
      operationId: createPlaylist
      summary: Create a playlist
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePlaylistRequest'
      responses:
        '200':
          description: Created playlist with tracks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaylistDetailResponse'
        '401':
          description: Authentication required
  /api/playlists/{id}:
    get:
      operationId: getPlaylist
      summary: Get a playlist by ID (with tracks)
      description: Visibility enforced (public / show_followers_only / owner).
      security: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Playlist detail with tracks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaylistDetailResponse'
        '404':
          description: Playlist not found
    put:
      operationId: updatePlaylist
      summary: Update a playlist (owner only)
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdatePlaylistRequest'
      responses:
        '200':
          description: Updated playlist with tracks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaylistDetailResponse'
        '401':
          description: Authentication required
        '403':
          description: Not the playlist owner
        '404':
          description: Playlist not found
    delete:
      operationId: deletePlaylist
      summary: Delete a playlist (owner only)
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: No content
        '401':
          description: Authentication required
        '403':
          description: Not the playlist owner
        '404':
          description: Playlist not found
  /api/playlists/{id}/tracks:
    get:
      operationId: getPlaylistTracks
      summary: Get tracks of a playlist
      security: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Array of playlist track items
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PlaylistTrackItem'
        '404':
          description: Playlist not found
    post:
      operationId: addPlaylistTracks
      summary: Add tracks to a playlist (owner only)
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [tracks]
              properties:
                tracks:
                  type: array
                  items:
                    $ref: '#/components/schemas/AddPlaylistTrackItem'
      responses:
        '200':
          description: Updated list of tracks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PlaylistTrackItem'
        '401':
          description: Authentication required
        '403':
          description: Not the playlist owner
  /api/playlists/{id}/tracks/{trackId}:
    delete:
      operationId: removePlaylistTrack
      summary: Remove a track from a playlist (owner only)
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
        - in: path
          name: trackId
          required: true
          schema:
            type: string
      responses:
        '204':
          description: No content
        '401':
          description: Authentication required
        '403':
          description: Not the playlist owner
  /api/playlists/{id}/tracks/reorder:
    put:
      operationId: reorderPlaylistTracks
      summary: Reorder tracks in a playlist (owner only)
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [order]
              properties:
                order:
                  type: array
                  items:
                    type: object
                    required: [musicTrackId]
                    properties:
                      musicTrackId:
                        type: string
      responses:
        '200':
          description: Updated list of tracks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PlaylistTrackItem'
        '401':
          description: Authentication required
        '403':
          description: Not the playlist owner
  /api/playlists/{id}/poster:
    post:
      operationId: uploadPlaylistPoster
      summary: Upload poster image for a playlist (owner only)
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        '200':
          description: Updated playlist with tracks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaylistDetailResponse'
        '401':
          description: Authentication required
        '403':
          description: Not the playlist owner

  /api/contact-history/conversation/{userId}:
    get:
      operationId: getConversationWithUser
      summary: Get message conversation with a specific user
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: userId
          required: true
          schema:
            type: string
            format: uuid
        - in: query
          name: limit
          schema:
            type: integer
            default: 200
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Conversation message records
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ContactHistoryResponse'
        '401':
          description: Authentication required
        '400':
          description: Invalid user ID

  # Push Notifications API (JWT Authentication)
  /api/pushnotifications/foruser/{id}:
    get:
      operationId: getNotificationsForUser
      summary: Get notifications for a user
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: User ID to fetch notifications for
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
          description: Max notifications to return
      responses:
        '200':
          description: Notifications for user
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PushNotificationResponse'
        '401':
          description: Missing or invalid JWT token
  /api/pushnotifications/foruser/me:
    get:
      operationId: getNotificationsForCurrentUser
      summary: Get notifications for the authenticated user
      description: |
        Fetches notifications for the currently authenticated user.
        This is a convenience endpoint that automatically uses the authenticated user's ID.
      security:
        - BearerAuth: []
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
          description: Max notifications to return
      responses:
        '200':
          description: Notifications for the authenticated user
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PushNotificationResponse'
              example:
                - id: "123e4567-e89b-12d3-a456-426614174000"
                  title: "Welcome to Pubfuse!"
                  body: "Welcome to Pubfuse! Your streaming platform is ready to go."
                  type: "social"
                  isRead: false
                  isSent: true
                  sentAt: "2024-01-01T00:00:00Z"
                  createdAt: "2024-01-01T00:00:00Z"
                  updatedAt: "2024-01-01T00:00:00Z"
        '401':
          description: Missing or invalid JWT token
  /api/pushnotifications/foruser/create:
    post:
      operationId: createNotification
      summary: Create a notification for the authenticated user
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PushNotificationCreateRequest'
      responses:
        '200':
          description: Notification created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PushNotificationResponse'
        '401':
          description: Missing or invalid JWT token
  /api/pushnotifications/{id}:
    put:
      operationId: updateNotification
      summary: Update a notification (mark as read)
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PushNotificationUpdateRequest'
      responses:
        '200':
          description: Updated notification
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PushNotificationResponse'
        '401':
          description: Missing or invalid JWT token
  /api/pushnotifications/{id}/delete:
    delete:
      operationId: deleteNotification
      summary: Delete a notification
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Deleted
        '401':
          description: Missing or invalid JWT token
  /api/pushnotifications/send:
    post:
      operationId: sendNotification
      summary: Send a push notification to a user
      description: |
        Sends a push notification to a user. The server currently saves the notification and returns 200 OK. 
        APNs delivery is configured separately via server cert/key configuration.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PushNotificationSendRequest'
            example:
              userID: "123e4567-e89b-12d3-a456-426614174000"
              title: "New follower"
              body: "Alex started following you"
              type: "social"
              data:
                screen: "profile"
                userId: "123e4567-e89b-12d3-a456-426614174000"
      responses:
        '200':
          description: Sent (no body)
        '401':
          description: Missing or invalid JWT token
  /api/pushnotifications/register-device-token:
    post:
      operationId: registerDeviceToken
      summary: Register device token for push notifications
      description: |
        Registers a device token for push notifications. The device token is stored in the user's profile
        and will be used to send push notifications to this device.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - deviceToken
              properties:
                deviceToken:
                  type: string
                  description: The device token from APNs
                  example: "5e0217086263a5efbc74b2993a4dec5b2fed1d91ffe74214d76dc258e53a870d"
            example:
              deviceToken: "5e0217086263a5efbc74b2993a4dec5b2fed1d91ffe74214d76dc258e53a870d"
      responses:
        '200':
          description: Device token registered successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - message
                properties:
                  success:
                    type: boolean
                    description: Whether the registration was successful
                    example: true
                  message:
                    type: string
                    description: Success message
                    example: "Device token registered successfully"
        '401':
          description: Missing or invalid JWT token
        '500':
          description: Internal server error

  /api/pushnotifications/register-device-token-public:
    post:
      operationId: registerDeviceTokenPublic
      summary: Register device token for push notifications (public)
      description: |
        Registers a device token for push notifications without authentication. This endpoint is used
        for initial device token registration before user login. The token is stored as inactive
        and will be activated when the user logs in.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - deviceToken
              properties:
                deviceToken:
                  type: string
                  description: The device token from APNs
                  example: "5e0217086263a5efbc74b2993a4dec5b2fed1d91ffe74214d76dc258e53a870d"
            example:
              deviceToken: "5e0217086263a5efbc74b2993a4dec5b2fed1d91ffe74214d76dc258e53a870d"
      responses:
        '200':
          description: Device token registered successfully (pending authentication)
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - message
                properties:
                  success:
                    type: boolean
                    description: Whether the registration was successful
                    example: true
                  message:
                    type: string
                    description: Success message
                    example: "Device token registered successfully. Will be activated when user logs in."
        '500':
          description: Internal server error

  # Debug and Development Endpoints
  /debug:
    get:
      operationId: debug
      summary: Debug information
      responses:
        '200':
          description: Debug data
          content:
            application/json:
              schema:
                type: object
  /test:
    get:
      operationId: test
      summary: Test endpoint - returns JSON status
      responses:
        '200':
          description: Test response with JSON data
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "success"
                  message:
                    type: string
                    example: "Test endpoint working"
                  timestamp:
                    type: string
                    format: date-time
  /debug-subdomain:
    get:
      operationId: debugSubdomain
      summary: Debug subdomain info
      responses:
        '200':
          description: Subdomain debug info
          content:
            application/json:
              schema:
                type: object
  /test-subdomain:
    get:
      operationId: testSubdomain
      summary: Test subdomain
      responses:
        '200':
          description: Subdomain test
          content:
            application/json:
              schema:
                type: object
  /test-db:
    get:
      operationId: testDb
      summary: Test database connection
      responses:
        '200':
          description: Database test result
          content:
            application/json:
              schema:
                type: object
  /test-connection:
    get:
      operationId: testConnection
      summary: Test connection
      responses:
        '200':
          description: Connection test result
          content:
            application/json:
              schema:
                type: object
  /test-sql:
    get:
      operationId: testSql
      summary: Test SQL queries
      responses:
        '200':
          description: SQL test result
          content:
            application/json:
              schema:
                type: object
  /test-insert:
    get:
      operationId: testInsert
      summary: Test data insertion
      responses:
        '200':
          description: Insert test result
          content:
            application/json:
              schema:
                type: object
  /simple-metrics:
    get:
      operationId: simpleMetrics
      summary: Simple metrics data
      responses:
        '200':
          description: Simple metrics
          content:
            application/json:
              schema:
                type: object

  # Database Management Endpoints
  /ensure-all-tables:
    get:
      operationId: ensureAllTables
      summary: Ensure all database tables exist
      responses:
        '200':
          description: Tables ensured
          content:
            application/json:
              schema:
                type: object
  /ensure-sdk-table:
    get:
      operationId: ensureSdkTable
      summary: Ensure SDK table exists
      responses:
        '200':
          description: SDK table ensured
          content:
            application/json:
              schema:
                type: object
  /ensure-app-ids-table:
    get:
      operationId: ensureAppIdsTable
      summary: Ensure app IDs table exists
      responses:
        '200':
          description: App IDs table ensured
          content:
            application/json:
              schema:
                type: object
  /ensure-users-table:
    get:
      operationId: ensureUsersTable
      summary: Ensure users table exists
      responses:
        '200':
          description: Users table ensured
          content:
            application/json:
              schema:
                type: object
  /ensure-broadcast-sessions-table:
    get:
      operationId: ensureBroadcastSessionsTable
      summary: Ensure broadcast sessions table exists
      responses:
        '200':
          description: Broadcast sessions table ensured
          content:
            application/json:
              schema:
                type: object
  /ensure-admin-table:
    get:
      operationId: ensureAdminTable
      summary: Ensure admin table exists
      responses:
        '200':
          description: Admin table ensured
          content:
            application/json:
              schema:
                type: object
  /check-tables:
    get:
      operationId: checkTables
      summary: Check all tables
      responses:
        '200':
          description: Table check result
          content:
            application/json:
              schema:
                type: object

  # Data Creation Endpoints
  /create-sdk-table:
    post:
      operationId: createSdkTable
      summary: Create SDK table
      responses:
        '200':
          description: SDK table created
          content:
            application/json:
              schema:
                type: object
  /create-app-ids-table:
    post:
      operationId: createAppIdsTable
      summary: Create app IDs table
      responses:
        '200':
          description: App IDs table created
          content:
            application/json:
              schema:
                type: object
  /create-users-table:
    post:
      operationId: createUsersTable
      summary: Create users table
      responses:
        '200':
          description: Users table created
          content:
            application/json:
              schema:
                type: object
  /create-sdk-clients-table:
    post:
      operationId: createSdkClientsTable
      summary: Create SDK clients table
      responses:
        '200':
          description: SDK clients table created
          content:
            application/json:
              schema:
                type: object
  /create-auth-tables:
    post:
      operationId: createAuthTables
      summary: Create authentication tables
      responses:
        '200':
          description: Auth tables created
          content:
            application/json:
              schema:
                type: object
  /create-simple-table:
    post:
      operationId: createSimpleTable
      summary: Create simple table
      responses:
        '200':
          description: Simple table created
          content:
            application/json:
              schema:
                type: object
  /create-broadcast-sessions:
    get:
      operationId: createBroadcastSessions
      summary: Create broadcast sessions
      responses:
        '200':
          description: Broadcast sessions created
          content:
            application/json:
              schema:
                type: object
  /create-test-session-simple:
    get:
      operationId: createTestSessionSimple
      summary: Create simple test session
      responses:
        '200':
          description: Test session created
          content:
            application/json:
              schema:
                type: object
  /create-test-session-direct:
    get:
      operationId: createTestSessionDirect
      summary: Create test session directly
      responses:
        '200':
          description: Test session created
          content:
            application/json:
              schema:
                type: object

  # Utility Endpoints
  /migrate:
    post:
      operationId: migrate
      summary: Run database migrations
      responses:
        '200':
          description: Migration completed
          content:
            application/json:
              schema:
                type: object
  /reset-circuit-breaker:
    post:
      operationId: resetCircuitBreaker
      summary: Reset circuit breaker
      responses:
        '200':
          description: Circuit breaker reset
          content:
            application/json:
              schema:
                type: object
  /ensure-indexes:
    post:
      operationId: ensureIndexes
      summary: Ensure database indexes
      responses:
        '200':
          description: Indexes ensured
          content:
            application/json:
              schema:
                type: object
  /fix-schema:
    post:
      operationId: fixSchema
      summary: Fix database schema
      responses:
        '200':
          description: Schema fixed
          content:
            application/json:
              schema:
                type: object
  /fix-stream-columns:
    post:
      operationId: fixStreamColumns
      summary: Fix stream columns
      responses:
        '200':
          description: Stream columns fixed
          content:
            application/json:
              schema:
                type: object
  /cleanup-sessions:
    post:
      operationId: cleanupSessions
      summary: Cleanup old sessions
      responses:
        '200':
          description: Sessions cleaned up
          content:
            application/json:
              schema:
                type: object

  # Session Management
  /sessions/{id}/error:
    post:
      operationId: sessionError
      summary: Report session error
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Error reported
          content:
            application/json:
              schema:
                type: object
  /sessions/{id}/heartbeat:
    post:
      operationId: sessionHeartbeat
      summary: Session heartbeat
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Heartbeat received
          content:
            application/json:
              schema:
                type: object

  # Ad Endpoints
  /ads/test/vast.xml:
    get:
      operationId: testVastXml
      summary: Test VAST XML
      responses:
        '200':
          description: VAST XML content
          content:
            application/xml:
              schema:
                type: string
  /ads/test/impression:
    get:
      operationId: testImpression
      summary: Test ad impression
      responses:
        '200':
          description: Impression tracked
          content:
            application/json:
              schema:
                type: object
  /ads/gam/vast-url:
    get:
      operationId: gamVastUrl
      summary: Get Google Ad Manager VAST URL
      description: |
        Returns a Google Ad Manager VAST tag URL for IMA integration.
        Supports both preroll and midroll ad types.
        
        **Ad Units:**
        - Preroll: `pubfuse_preroll` (ID: 23317542294)
        - Midroll: `pubfuse_preroll2` (ID: 23318469755)
        
        **Usage:**
        - Preroll: `/ads/gam/vast-url?type=preroll`
        - Midroll: `/ads/gam/vast-url?type=midroll`
        
        The endpoint uses the `GOOGLE_AD_MANAGER_NETWORK_CODE` environment variable (default: 23318538004).
      parameters:
        - in: query
          name: type
          required: false
          schema:
            type: string
            enum: [preroll, midroll]
            default: preroll
          description: Ad type - "preroll" for pre-roll ads, "midroll" for mid-roll ads
      responses:
        '200':
          description: VAST tag URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  adTagUrl:
                    type: string
                    format: uri
                    description: Complete VAST tag URL for Google Ad Manager
                    example: "https://pubads.g.doubleclick.net/gampad/ads?iu=/23318538004/pubfuse_preroll&description_url=https%3A%2F%2Fpubfuse.com&tfcd=0&npa=0&sz=400x300%7C640x480&ciu_szs=480x320%2C1024x768&gdfp_req=1&unviewed_position_start=1&output=vast&env=vp&impl=s&correlator=1234567890"
        '500':
          description: Google Ad Manager Network Code not configured
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Google Ad Manager Network Code not configured"
  /ads/hilltop/vast-url:
    get:
      operationId: hilltopVastUrl
      summary: Hilltop VAST URL
      responses:
        '200':
          description: VAST URL
          content:
            application/json:
              schema:
                type: object
  /ads/hilltop/vast.xml:
    get:
      operationId: hilltopVastXml
      summary: Hilltop VAST XML
      responses:
        '200':
          description: Hilltop VAST XML
          content:
            application/xml:
              schema:
                type: string

  # Other Endpoints
  /startBroadcast:
    get:
      operationId: startBroadcast
      summary: Start broadcast page
      responses:
        '200':
          description: Start broadcast page
          content:
            text/html:
              schema:
                type: string
  /logout:
    get:
      operationId: logout
      summary: User logout
      responses:
        '200':
          description: Logout successful
          content:
            application/json:
              schema:
                type: object
  /testcomponent:
    get:
      operationId: testComponent
      summary: Test component page
      responses:
        '200':
          description: Test component page
          content:
            text/html:
              schema:
                type: string
  /testadcomponent:
    get:
      operationId: testAdComponent
      summary: Test ad component page
      responses:
        '200':
          description: Test ad component page
          content:
            text/html:
              schema:
                type: string
  /favicon.ico:
    get:
      operationId: favicon
      summary: Favicon
      responses:
        '200':
          description: Favicon
          content:
            image/x-icon:
              schema:
                type: string

  # External API Endpoints (Public Access)
  /api/external/sessions:
    get:
      operationId: getExternalSessions
      summary: Get available sessions (external/public access)
      description: Main endpoint for external devices and public access to browse available streaming sessions
      parameters:
        - in: query
          name: status
          schema:
            type: string
            enum: [pending, active, paused, completed, error, cancelled, private]
            default: active
        - in: query
          name: visibility
          schema:
            type: string
            enum: [public, private]
            default: public
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Available sessions for external access
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ExternalSessionResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Database not available"

  /api/external/sessions/public:
    get:
      operationId: getPublicSessions
      summary: Get public sessions only
      description: Retrieve only public sessions (visibility = public)
      parameters:
        - in: query
          name: status
          schema:
            type: string
            enum: [pending, active, paused, completed, error, cancelled]
            default: active
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Public sessions only
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ExternalSessionResponse'
        '500':
          description: Server error

  /api/external/sessions/featured:
    get:
      operationId: getFeaturedSessions
      summary: Get featured sessions
      description: Retrieve featured sessions (isFeatured = true)
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            default: 20
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Featured sessions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ExternalSessionResponse'
        '500':
          description: Server error

  /api/external/sessions/search:
    get:
      operationId: searchSessions
      summary: Search sessions
      description: Search sessions by title and tags
      parameters:
        - in: query
          name: q
          schema:
            type: string
          description: Search query for title
        - in: query
          name: tags
          schema:
            type: array
            items:
              type: string
          description: Filter by tags
        - in: query
          name: limit
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ExternalSessionResponse'
        '500':
          description: Server error

  /api/external/sessions/{id}/join-info:
    get:
      operationId: getJoinInfo
      summary: Get session join information
      description: Get join URLs and session information for a specific session
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Session UUID
      responses:
        '200':
          description: Join information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JoinInfoResponse'
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Session not found"
        '500':
          description: Server error

  # Session Tracking Endpoints
  /api/sessions/{id}/viewer/join:
    post:
      operationId: trackViewerJoin
      summary: Track viewer joining a session
      description: Increment viewer count and add user to guest list when joining a session
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Session UUID
      responses:
        '200':
          description: Viewer join tracked successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: "Viewer join tracked successfully"
                  viewerCount:
                    type: integer
                    example: 15
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Authentication required"
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Session not found"
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Database update failed"

  /api/sessions/{id}/viewer/leave:
    post:
      operationId: trackViewerLeave
      summary: Track viewer leaving a session
      description: Decrement viewer count and remove user from guest list when leaving a session
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Session UUID
      responses:
        '200':
          description: Viewer leave tracked successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: "Viewer leave tracked successfully"
                  viewerCount:
                    type: integer
                    example: 14
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Authentication required"
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Session not found"
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Database update failed"

  /api/sessions/{id}/cohost/join:
    post:
      operationId: trackCohostJoin
      summary: Track cohost joining a session
      description: Add user to cohost list when joining as cohost
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Session UUID
      responses:
        '200':
          description: Cohost join tracked successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: "Cohost join tracked successfully"
                  cohostCount:
                    type: integer
                    example: 2
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Authentication required"
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Session not found"
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Database update failed"

  /api/sessions/{id}/cohost/leave:
    post:
      operationId: trackCohostLeave
      summary: Track cohost leaving a session
      description: Remove user from cohost list when leaving cohost role
      security:
        - BearerAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            format: uuid
          description: Session UUID
      responses:
        '200':
          description: Cohost leave tracked successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: "Cohost leave tracked successfully"
                  cohostCount:
                    type: integer
                    example: 1
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Authentication required"
        '404':
          description: Session not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Session not found"
        '500':
          description: Server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: boolean
                    example: true
                  reason:
                    type: string
                    example: "Database update failed"

components:
  securitySchemes:
    HMACSignature:
      type: apiKey
      in: header
      name: X-API-Key
      description: HMAC Signature Authentication - Requires X-API-Key, X-Signature, and X-Timestamp headers
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: "JWT token from username/password login at /api/users/login. Use in Authorization header: \"Bearer <token>\""
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key for authentication
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT token for user authentication
  schemas:
    # ICE Configuration
    ICEConfig:
      type: object
      properties:
        iceServers:
          type: array
          items:
            $ref: '#/components/schemas/ICEServer'
    ICEServer:
      type: object
      properties:
        urls:
          type: array
          items:
            type: string
        username:
          type: string
        credential:
          type: string

    # Streaming & LiveKit Schemas
    StreamingProvider:
      type: object
      description: Streaming provider configuration
      properties:
        id:
          type: string
          description: Provider identifier (e.g., "livekit", "webrtc")
          example: "livekit"
        name:
          type: string
          description: Human-readable provider name
          example: "LiveKit SFU"
        type:
          type: string
          description: Provider type
          example: "webrtc"
        isConfigured:
          type: boolean
          description: Whether the provider is properly configured
          example: true
        configuration:
          type: object
          nullable: true
          description: Provider-specific configuration
          properties:
            url:
              type: string
              description: LiveKit server URL
              example: "wss://pubfusestream-ctq7dz1g.livekit.cloud"
            apiKey:
              type: string
              description: LiveKit API key
            apiSecret:
              type: string
              description: LiveKit API secret
            turnServers:
              type: array
              items:
                type: string
              description: TURN server URLs
    CreateStreamingSessionRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
          description: Session title
          example: "My Live Stream"
        description:
          type: string
          nullable: true
          description: Session description
        visibility:
          type: string
          enum: [public, private, unlisted]
          default: public
          description: Session visibility
        maxParticipants:
          type: integer
          default: 1000
          description: Maximum number of participants
        metadata:
          type: object
          nullable: true
          description: Additional metadata
          additionalProperties: true
    StreamingSessionResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Session ID
        title:
          type: string
          description: Session title
        description:
          type: string
          nullable: true
          description: Session description
        status:
          type: string
          enum: [active, completed, error, pending, paused, cancelled]
          description: Session status
        visibility:
          type: string
          enum: [public, private, unlisted]
          description: Session visibility
        maxParticipants:
          type: integer
          description: Maximum participants
        participantCount:
          type: integer
          description: Current participant count
        rtmpUrl:
          type: string
          nullable: true
          description: RTMP ingest URL
          example: "rtmp://ingest.pubfuse.com/live/stream-key"
        hlsUrl:
          type: string
          nullable: true
          description: HLS playback URL
          example: "https://cdn.pubfuse.com/hls/stream-key.m3u8"
        webrtcEndpoint:
          type: string
          nullable: true
          description: WebRTC endpoint URL
        livekitRoom:
          type: string
          nullable: true
          description: LiveKit room name
          example: "session-uuid"
        streamKey:
          type: string
          nullable: true
          description: Stream key for RTMP ingest
        watchUrl:
          type: string
          nullable: true
          description: Watch page URL
          example: "/streams/session-uuid/watch"
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp
        metadata:
          type: object
          nullable: true
          description: Additional metadata
          additionalProperties: true
    TokenRequest:
      type: object
      required: [userId, role]
      description: Request for LiveKit JWT token generation
      properties:
        userId:
          type: string
          format: uuid
          description: User ID of the participant
          example: "123e4567-e89b-12d3-a456-426614174000"
        role:
          type: string
          enum: [publisher, subscriber, both]
          description: Participant role
          example: "subscriber"
        name:
          type: string
          nullable: true
          description: Participant display name
          example: "John Doe"
    LiveKitTokenResponse:
      type: object
      description: LiveKit JWT token response
      properties:
        token:
          type: string
          description: LiveKit-compatible JWT token
          example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
        expiresAt:
          type: string
          format: date-time
          description: Token expiration time
        room:
          type: string
          description: LiveKit room name
          example: "session-uuid"
        serverUrl:
          type: string
          nullable: true
          description: LiveKit server URL
          example: "wss://pubfusestream-ctq7dz1g.livekit.cloud"

    # Ad Configuration
    AdConfig:
      type: object
      properties:
        enabled:
          type: boolean
        providers:
          type: array
          items:
            type: string
    VersionConfig:
      type: object
      properties:
        version:
          type: string
        build:
          type: string

    # User Authentication
    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
    LoginResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/LoginData'
    LoginData:
      type: object
      properties:
        token:
          type: string
        user:
          $ref: '#/components/schemas/UserData'
    UserData:
      type: object
      properties:
        id:
          type: string
          format: uuid
        username:
          type: string
        email:
          type: string
          format: email
    SignupRequest:
      type: object
      required: [username, email, password]
      properties:
        username:
          type: string
        email:
          type: string
          format: email
        password:
          type: string
        displayName:
          type: string
    SignupResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/SignupData'
    SignupData:
      type: object
      properties:
        id:
          type: string
          format: uuid
        username:
          type: string
        email:
          type: string
          format: email
    LogoutRequest:
      type: object
      required: [token]
      properties:
        token:
          type: string
          description: Authentication token to invalidate
        deviceToken:
          type: string
          description: Optional device token to deactivate on logout for additional security
    LogoutResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/LogoutData'
    LogoutData:
      type: object
      properties:
        message:
          type: string

    # Broadcast Schemas
    BroadcastCreateRequest:
      type: object
      properties:
        title:
          type: string
        description:
          type: string
    BroadcastResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Broadcast session ID (use for status updates, heartbeats, viewer counts)
          example: "7e5a46db-ff1d-417e-bf5b-33fe9a5b13ec"
        streamId:
          type: string
          format: uuid
          nullable: true
          description: |
            **LiveKit Room Name** - This is the stream ID used for LiveKit WebRTC connections.
            The streamId is stored in broadcast_sessions.stream_id and is used as the LiveKit room name.
            Use this value when connecting to LiveKit rooms and generating JWT tokens.
          example: "3af727c6-cd80-414e-83e1-b2ba90d32b8f"
        title:
          type: string
          description: Broadcast title
        hostUsername:
          type: string
          nullable: true
          description: Host username
        hostDisplayName:
          type: string
          nullable: true
          description: Host display name
        hostNickname:
          type: string
          nullable: true
          description: Host nickname
        description:
          type: string
          nullable: true
          description: Broadcast description
        status:
          type: string
          enum: [created, active, paused, completed, error, cancelled]
          description: Current broadcast status
        visibility:
          type: string
          enum: [public, private]
          description: Broadcast visibility
        rtmpUrl:
          type: string
          nullable: true
          description: RTMP ingest URL for external encoders
        hlsUrl:
          type: string
          nullable: true
          description: HLS playback URL
        watchUrl:
          type: string
          nullable: true
          description: Web viewer URL
        streamKey:
          type: string
          nullable: true
          description: RTMP stream key (UUID without dashes)
        isFeatured:
          type: boolean
          description: Whether the broadcast is featured
        isPhoneSession:
          type: boolean
          description: "True if this is a phone call session"
        isMessageSession:
          type: boolean
          description: "True if this is a messaging session"
        viewerCount:
          type: integer
          description: Current number of viewers
        maxViewers:
          type: integer
          description: Maximum allowed viewers
        createdAt:
          type: string
          format: date-time
          description: When the broadcast was created
        updatedAt:
          type: string
          format: date-time
          nullable: true
          description: When the broadcast was last updated
        startedAt:
          type: string
          format: date-time
          nullable: true
          description: When the broadcast started
        endedAt:
          type: string
          format: date-time
          nullable: true
          description: When the broadcast ended
        recordingUrl:
          type: string
          nullable: true
          description: URL to the recorded video file (from broadcast_sessions.recording_url or files.file_path_original)
          example: "https://pubfuse-streaming-bucket.nyc3.cdn.digitalocean.com/recordings/broadcasts/..."
        isRecording:
          type: boolean
          nullable: true
          description: Whether recording is currently active
        livekitEgressId:
          type: string
          nullable: true
          description: |
            LiveKit egress ID if recording is active.
            
            **Recording Methods:**
            - **WebEgress** (Default): Records viewer page with all participants, reactions, and UI elements.
              Configured via `LIVEKIT_RECORDING_METHOD=web_egress` environment variable.
              Viewer URL format: `{PUBFUSE_BASE_URL}/streams/{streamId}/watch`
            - **Room Composite**: Records directly from SFU (faster, no reactions/UI).
              Configured via `LIVEKIT_RECORDING_METHOD=room_composite` environment variable.
            
            **Environment Variables:**
            - `LIVEKIT_RECORDING_METHOD`: Recording method (`web_egress` or `room_composite`, default: `web_egress`)
            - `PUBFUSE_BASE_URL`: Base URL for WebEgress viewer page (optional, default: `https://www.pubfuse.com`)
        hasFileEntry:
          type: boolean
          description: Whether a file entry exists in the files table for this recording
    BroadcastListResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
        createdAt:
          type: string
          format: date-time
    BroadcastDetailResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        description:
          type: string
        status:
          type: string
        rtmpUrl:
          type: string
        streamKey:
          type: string
        viewerCount:
          type: integer
        createdAt:
          type: string
          format: date-time
    StatusUpdateRequest:
      type: object
      required: [status]
      properties:
        status:
          type: string
    StatusUpdateResponse:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string

    # Stream Schemas
    StreamListResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
        streamUrl:
          type: string
        createdAt:
          type: string
          format: date-time
    StreamDetailResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
        streamUrl:
          type: string
        viewerCount:
          type: integer
        createdAt:
          type: string
          format: date-time
    TokenMintRequest:
      type: object
      properties:
        amount:
          type: integer

    # Legacy Stream Schemas
    StreamCreateRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
    StreamResponse:
      type: object
      required: [id, title]
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
    TokenResponse:
      type: object
      required: [token]
      properties:
        token:
          type: string
        amount:
          type: integer
        expiresAt:
          type: string
          format: date-time

    # Admin Schemas
    SDKClientRegistrationRequest:
      type: object
      required: [name, website, description, contactName, contactTitle, contactEmail, contactPhone, expectedApps, useCase, expectedUsers, agreeTerms, agreeMarketing, agreeDataProcessing]
      properties:
        name:
          type: string
        website:
          type: string
        description:
          type: string
        contactName:
          type: string
        contactTitle:
          type: string
        contactEmail:
          type: string
          format: email
        contactPhone:
          type: string
        expectedApps:
          type: integer
        useCase:
          type: string
        expectedUsers:
          type: integer
        agreeTerms:
          type: boolean
        agreeMarketing:
          type: boolean
        agreeDataProcessing:
          type: boolean
    SDKClientResponse:
      type: object
      description: SDK Client response with ad settings
      properties:
        id:
          type: string
          format: uuid
          description: SDK Client ID
        name:
          type: string
          description: Client name
        description:
          type: string
          nullable: true
          description: Client description
        apiKey:
          type: string
          description: API key for authentication
        isActive:
          type: boolean
          description: Whether the client is active
        webhookUrl:
          type: string
          nullable: true
          format: uri
          description: Webhook URL for notifications
        allowedOrigins:
          type: array
          items:
            type: string
          description: Allowed CORS origins
        rateLimitPerHour:
          type: integer
          description: Rate limit per hour
        enableAdFree:
          type: boolean
          description: Enable ad-free option for this client (allows clients to create events without ads)
        adPrerollEnabled:
          type: boolean
          description: Enable preroll ads at beginning of streams
        adIntervalMinutes:
          type: integer
          nullable: true
          description: Interval in minutes between ad breaks
        adCountPerInterval:
          type: integer
          nullable: true
          description: Number of ads to play per interval
        adSkipForPaidUsers:
          type: boolean
          description: Skip ads for paid users
        createdAt:
          type: string
          format: date-time
          description: Creation timestamp
        updatedAt:
          type: string
          format: date-time
          description: Last update timestamp
        userCount:
          type: integer
          description: Number of users for this client
        appIdCount:
          type: integer
          description: Number of app IDs for this client
    UserSignupRequest:
      type: object
      required: [username, email, password]
      properties:
        username:
          type: string
        email:
          type: string
          format: email
        password:
          type: string
    UserResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        username:
          type: string
        email:
          type: string
          format: email
        createdAt:
          type: string
          format: date-time
    UserLoginRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
    UserLoginResponse:
      type: object
      properties:
        token:
          type: string
          description: JWT token for authenticated requests
          example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
        user:
          type: object
          properties:
            id:
              type: string
              format: uuid
              example: "123e4567-e89b-12d3-a456-426614174000"
            username:
              type: string
              example: "tester"
            email:
              type: string
              format: email
              example: "tester@pubfuse.com"
            displayName:
              type: string
              nullable: true
              example: "Test User"
            firstName:
              type: string
              nullable: true
              example: "Test"
            lastName:
              type: string
              nullable: true
              example: "User"
            profileImageUrl:
              type: string
              nullable: true
              example: null
            isActive:
              type: boolean
              example: true
            createdAt:
              type: string
              format: date-time
              example: "2024-01-01T00:00:00Z"
            updatedAt:
              type: string
              format: date-time
              example: "2024-01-01T00:00:00Z"
    ForgotPasswordRequest:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
          description: User's email address
          example: "user@example.com"
    ResetPasswordRequest:
      type: object
      required: [token, password, confirmPassword]
      properties:
        token:
          type: string
          description: Password reset token received via email
          example: "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6"
        password:
          type: string
          minLength: 8
          description: New password (minimum 8 characters)
          example: "newpassword123"
        confirmPassword:
          type: string
          minLength: 8
          description: Password confirmation (must match password)
          example: "newpassword123"
    AppIdResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        appId:
          type: string
        createdAt:
          type: string
          format: date-time

    # Session Schemas
    SessionResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        description:
          type: string
        status:
          type: string
        visibility:
          type: string
        viewerCount:
          type: integer
        maxViewers:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    # External Device Schemas
    ExternalSessionResponse:
      type: object
      required: [id, title, status, visibility, viewerCount, maxViewers, joinUrl, isFeatured]
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
          enum: [pending, active, paused, completed, error, cancelled, private]
        visibility:
          type: string
          enum: [public, private]
        viewerCount:
          type: integer
        maxViewers:
          type: integer
        hlsUrl:
          type: string
          nullable: true
        rtmpUrl:
          type: string
          nullable: true
        webrtcEndpoint:
          type: string
          nullable: true
        joinUrl:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        tags:
          type: array
          items:
            type: string
        isFeatured:
          type: boolean
        isPhoneSession:
          type: boolean
          description: "True if this is a phone call session (should be hidden from public listings)"
        isMessageSession:
          type: boolean
          description: "True if this is a messaging session (should be hidden from public listings)"
        totalSparksReceived:
          type: integer
        totalDiamondsReceived:
          type: integer
        peakViewerCount:
          type: integer
        totalAdViews:
          type: integer
          description: Total number of ad views (preroll + midroll)
          example: 42
        totalAdImpressions:
          type: integer
          description: Total number of ad impressions (when ads are loaded)
          example: 45
        totalPrerollViews:
          type: integer
          description: Total number of preroll ad views
          example: 25
        totalMidrollViews:
          type: integer
          description: Total number of midroll ad views
          example: 17

    JoinInfoResponse:
      type: object
      required: [sessionId, title, status, visibility, joinUrl, viewerCount, maxViewers, isJoinable]
      properties:
        sessionId:
          type: string
          format: uuid
          description: Session UUID
        title:
          type: string
          description: Session title
        status:
          type: string
          enum: [pending, active, paused, completed, error, cancelled, private]
          description: Session status
        visibility:
          type: string
          enum: [public, private]
          description: Session visibility
        hlsUrl:
          type: string
          nullable: true
          description: HLS streaming URL
        webrtcEndpoint:
          type: string
          nullable: true
          description: WebRTC endpoint URL
        joinUrl:
          type: string
          description: Join URL for the session
        viewerCount:
          type: integer
          description: Current number of viewers
        maxViewers:
          type: integer
          description: Maximum allowed viewers
        isJoinable:
          type: boolean
          description: Whether the session can be joined
        requiresSubscription:
          type: boolean
          description: Whether a subscription is required to join
        requiresTokens:
          type: boolean
          description: Whether tokens are required to join
        subscriptionTierRequired:
          type: string
          nullable: true
          description: Required subscription tier
        accessTokensRequired:
          type: integer
          nullable: true
          description: Number of access tokens required

    SessionDetailResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        description:
          type: string
        status:
          type: string
        visibility:
          type: string
        viewerCount:
          type: integer
        maxViewers:
          type: integer
        peakViewerCount:
          type: integer
        totalWatchTimeMinutes:
          type: integer
        totalRevenueUsd:
          type: number
        totalSparksReceived:
          type: integer
        totalDiamondsReceived:
          type: integer
        totalReactions:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    # User Profile & Social Features
    FullUserProfileResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        username:
          type: string
        email:
          type: string
          format: email
        phoneNumber:
          type: string
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        avatarUrl:
          type: string
          nullable: true
        isActive:
          type: boolean
        emailVerified:
          type: boolean
        phoneVerified:
          type: boolean
        appIdName:
          type: string
        followerCount:
          type: integer
        followingCount:
          type: integer
        connectionCount:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    
    UserProfileResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        username:
          type: string
        email:
          type: string
          format: email
        phoneNumber:
          type: string
          nullable: true
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        avatarUrl:
          type: string
          nullable: true
        isActive:
          type: boolean
        emailVerified:
          type: boolean
        phoneVerified:
          type: boolean
        appIdName:
          type: string
        followerCount:
          type: integer
        followingCount:
          type: integer
        connectionCount:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    FollowCountResponse:
      type: object
      properties:
        count:
          type: integer
    UserSearchResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        username:
          type: string
        email:
          type: string
          format: email
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        avatarUrl:
          type: string
          nullable: true
        isFollowing:
          type: boolean
    
    UserBasicResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        username:
          type: string
      required:
        - id
        - username
    
    UserFollowResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        follower:
          $ref: '#/components/schemas/UserBasicResponse'
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - follower
        - createdAt

    # User profile update & security
    UpdateUserProfileRequest:
      type: object
      properties:
        username:
          type: string
          nullable: true
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        avatarUrl:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        phoneNumber:
          type: string
          nullable: true
    ChangePasswordRequest:
      type: object
      required: [currentPassword, newPassword]
      properties:
        currentPassword:
          type: string
        newPassword:
          type: string
    UserBanResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: "User blocked successfully"
    BlockUserRequest:
      type: object
      properties:
        reason:
          type: string
          description: Optional reason supplied by the blocking user
    MusicUserLinkResponse:
      type: object
      description: Music user link response
      properties:
        id:
          type: string
          format: uuid
          description: Link ID
        pubfuseUserId:
          type: string
          format: uuid
          description: pubfuse.com user ID
        pubfuseUserEmail:
          type: string
          format: email
          description: pubfuse.com user email
        pubfuseMusicUserId:
          type: string
          nullable: true
          description: pubfu.se music user ID
        isActive:
          type: boolean
          description: Whether the link is active
        linkedAt:
          type: string
          format: date-time
          nullable: true
          description: When the link was created
        lastSyncedAt:
          type: string
          format: date-time
          nullable: true
          description: Last synchronization timestamp
        metadata:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: Additional metadata about the link
        createdAt:
          type: string
          format: date-time
          nullable: true
    MusicUserSearchResult:
      type: object
      description: Search result for a pubfu.se user
      properties:
        id:
          type: string
          format: uuid
          description: pubfu.se user ID
        email:
          type: string
          format: email
          description: User's email address
        nickname:
          type: string
          nullable: true
          description: User's nickname (if available)
        activated:
          type: boolean
          description: Whether the user account is activated
    LinkExistingMusicUserRequest:
      type: object
      required:
        - musicUserId
      properties:
        musicUserId:
          type: string
          format: uuid
          description: The pubfu.se user ID to link to
        musicUserEmail:
          type: string
          format: email
          nullable: true
          description: |
            The pubfu.se user's email address (optional, but recommended).
            Can be provided from search results to optimize the password verification process.
            If not provided, the server will attempt to fetch it from pubfu.se.
        password:
          type: string
          format: password
          nullable: true
          description: |
            Password for the pubfu.se account (required if emails don't match).
            If the pubfu.se user's email matches the pubfuse.com user's email, this field is optional.
            If emails don't match, this field is required to verify ownership of the pubfu.se account.
            Password verification is performed by calling pubfu.se's POST /gettoken/:email/:pass/ endpoint.
    BlockedUserResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        username:
          type: string
        reason:
          type: string
          nullable: true
        blockedAt:
          type: string
          format: date-time
    DeleteAccountResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: "Account deleted successfully"
    ContentReportRequest:
      type: object
      required: [contentType, reason]
      properties:
        contentType:
          type: string
          example: "stream"
        contentId:
          type: string
          nullable: true
          example: "a3a2b064-52c5-4bde-93fd-1fb6babb0f48"
        reason:
          type: string
          example: "Contains hateful speech"
        details:
          type: string
          nullable: true
          example: "Occurred around the 12 minute mark."
        metadata:
          type: object
          additionalProperties:
            type: string
          nullable: true
    ContentReportResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        contentType:
          type: string
        contentId:
          type: string
          nullable: true
        reason:
          type: string
        details:
          type: string
          nullable: true
        status:
          type: string
          enum: [pending, reviewed, actionTaken]
        metadata:
          type: object
          additionalProperties:
            type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
    SimpleSuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string

    # Push Notifications
    PushNotificationResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        userId:
          type: string
          format: uuid
        title:
          type: string
        body:
          type: string
        type:
          type: string
          description: e.g., message, call, social, promotion
        data:
          type: string
          nullable: true
          description: Opaque JSON string stored by server
        isRead:
          type: boolean
        isSent:
          type: boolean
        sentAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    PushNotificationCreateRequest:
      type: object
      required: [title, body, type]
      properties:
        title:
          type: string
        body:
          type: string
        type:
          type: string
        data:
          type: object
          additionalProperties:
            type: string
          nullable: true

    PushNotificationUpdateRequest:
      type: object
      properties:
        isRead:
          type: boolean

    PushNotificationSendRequest:
      type: object
      required: [userID, title, body, type]
      properties:
        userID:
          type: string
          format: uuid
        title:
          type: string
        body:
          type: string
        type:
          type: string
        data:
          type: object
          additionalProperties:
            type: string
          nullable: true
        targetPlatform:
          type: string
          description: Limit send to specific platform; "all" (default), "ios", or "android"
          enum: [all, ios, android]
          nullable: true

    # Contacts & Sync Features
    ContactsSyncRequest:
      type: object
      required: [contacts]
      properties:
        contacts:
          type: array
          items:
            $ref: '#/components/schemas/ContactSyncRequest'
    ContactSyncRequest:
      type: object
      required: [phoneNumber]
      properties:
        phoneNumber:
          type: string
          description: Raw phone number from device
        displayName:
          type: string
          nullable: true
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        email:
          type: string
          format: email
          nullable: true
    ContactsSyncResponse:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string
        contacts:
          type: array
          items:
            $ref: '#/components/schemas/ContactResponse'
        totalContacts:
          type: integer
        pubfuseUsers:
          type: integer
    ContactResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        phoneNumber:
          type: string
        displayName:
          type: string
          nullable: true
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        email:
          type: string
          format: email
          nullable: true
        isPubfuseUser:
          type: boolean
        pubfuseUserId:
          type: string
          format: uuid
          nullable: true
        isFollowing:
          type: boolean
        isFollowedBy:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    ContactUpdateRequest:
      type: object
      properties:
        phoneNumber:
          type: string
        displayName:
          type: string
          nullable: true
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        email:
          type: string
          format: email
          nullable: true
    SyncedContactResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        contactId:
          type: string
          format: uuid
        syncStatus:
          type: string
          enum: [pending, synced, failed]
        syncAttempts:
          type: integer
        lastSyncAt:
          type: string
          format: date-time
          nullable: true
        syncError:
          type: string
          nullable: true
        autoFollowEnabled:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        contact:
          $ref: '#/components/schemas/ContactResponse'
    ContactSyncStatusResponse:
      type: object
      properties:
        contactId:
          type: string
          format: uuid
        isPubfuseUser:
          type: boolean
        pubfuseUserId:
          type: string
          format: uuid
          nullable: true
        isFollowing:
          type: boolean
        isFollowedBy:
          type: boolean
        lastUpdated:
          type: string
          format: date-time
    
    # File Management Schemas
    FileResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        userID:
          type: string
          format: uuid
        forContactID:
          type: string
          format: uuid
          nullable: true
        name:
          type: string
        description:
          type: string
          nullable: true
        mimeType:
          type: string
        filePathOriginal:
          type: string
        filePathThumbnail:
          type: string
          nullable: true
        filePathSmall:
          type: string
          nullable: true
        filePathMedium:
          type: string
          nullable: true
        filePathLarge:
          type: string
          nullable: true
        fileSizeBytes:
          type: integer
          format: int64
        categoryName:
          type: string
          nullable: true
        tags:
          type: array
          items:
            type: string
          nullable: true
        location:
          type: string
          nullable: true
        forMessageId:
          type: string
          format: uuid
          nullable: true
        forCallId:
          type: string
          format: uuid
          nullable: true
        forBroadcastId:
          type: string
          format: uuid
          nullable: true
        waveformUrl:
          type: string
          nullable: true
          description: URL to the waveform image (PNG) generated for audio files. Automatically created when audio files are uploaded.
        forId:
          type: string
          format: uuid
          nullable: true
          description: Generic ID field to associate files with objects (e.g., track ID, note ID). Used to link waveforms to their related objects.
        createdAt:
          type: string
          format: date-time
          nullable: true
        updatedAt:
          type: string
          format: date-time
          nullable: true
    
    FileCreateRequest:
      type: object
      required: [name, mimeType, filePathOriginal, fileSizeBytes]
      properties:
        name:
          type: string
        description:
          type: string
          nullable: true
        mimeType:
          type: string
        filePathOriginal:
          type: string
        forContactID:
          type: string
          format: uuid
          nullable: true
        filePathThumbnail:
          type: string
          nullable: true
        filePathSmall:
          type: string
          nullable: true
        filePathMedium:
          type: string
          nullable: true
        filePathLarge:
          type: string
          nullable: true
        fileSizeBytes:
          type: integer
          format: int64
        categoryName:
          type: string
          nullable: true
        tags:
          type: array
          items:
            type: string
          nullable: true
        location:
          type: string
          nullable: true
        forMessageId:
          type: string
          format: uuid
          nullable: true
        forCallId:
          type: string
          format: uuid
          nullable: true
        forBroadcastId:
          type: string
          format: uuid
          nullable: true
    
    FileUpdateRequest:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
          nullable: true
        tags:
          type: array
          items:
            type: string
          nullable: true
        location:
          type: string
          nullable: true
        categoryName:
          type: string
          nullable: true
    
    # Contact History Schemas
    ContactHistoryResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        isCall:
          type: boolean
        isMessage:
          type: boolean
        callDirection:
          type: string
          enum: [inbound, outbound]
          nullable: true
        isCallAnswered:
          type: boolean
          nullable: true
        hostUserId:
          type: string
          format: uuid
        hostContactId:
          type: string
          format: uuid
          nullable: true
        message:
          type: string
          nullable: true
        subject:
          type: string
          nullable: true
        parentMessageId:
          type: string
          format: uuid
          nullable: true
        approvedUserIds:
          type: array
          items:
            type: string
          nullable: true
        approvedUserNames:
          type: array
          items:
            type: string
          nullable: true
        attendeeIds:
          type: array
          items:
            type: string
          nullable: true
        attendeeNames:
          type: array
          items:
            type: string
          nullable: true
        bannedUserIds:
          type: array
          items:
            type: string
          nullable: true
        location:
          type: string
          nullable: true
        startTime:
          type: string
          format: date-time
        endTime:
          type: string
          format: date-time
          nullable: true
        relatedBroadcastId:
          type: string
          format: uuid
          nullable: true
        relatedStreamId:
          type: string
          format: uuid
          nullable: true
        createdAt:
          type: string
          format: date-time
          nullable: true
        updatedAt:
          type: string
          format: date-time
          nullable: true
        isNote:
          type: boolean
          description: Indicates whether this contact history entry is a note
    CreateContactHistoryRequest:
      type: object
      required:
        - isCall
        - isMessage
        - hostUserId
        - startTime
      properties:
        isCall:
          type: boolean
        isMessage:
          type: boolean
        callDirection:
          type: string
          enum: [inbound, outbound]
        isCallAnswered:
          type: boolean
        hostUserId:
          type: string
          format: uuid
        hostContactId:
          type: string
          format: uuid
        message:
          type: string
        subject:
          type: string
        parentMessageId:
          type: string
          format: uuid
        approvedUserIds:
          type: array
          items:
            type: string
        approvedUserNames:
          type: array
          items:
            type: string
        attendeeIds:
          type: array
          items:
            type: string
        attendeeNames:
          type: array
          items:
            type: string
        bannedUserIds:
          type: array
          items:
            type: string
        location:
          type: string
        startTime:
          type: string
          format: date-time
        endTime:
          type: string
          format: date-time
        relatedBroadcastId:
          type: string
          format: uuid
        relatedStreamId:
          type: string
          format: uuid
    UpdateContactHistoryRequest:
      type: object
      properties:
        isCall:
          type: boolean
        isMessage:
          type: boolean
        callDirection:
          type: string
          enum: [inbound, outbound]
        isCallAnswered:
          type: boolean
        hostUserId:
          type: string
          format: uuid
        hostContactId:
          type: string
          format: uuid
        message:
          type: string
        subject:
          type: string
        parentMessageId:
          type: string
          format: uuid
        approvedUserIds:
          type: array
          items:
            type: string
        approvedUserNames:
          type: array
          items:
            type: string
        attendeeIds:
          type: array
          items:
            type: string
        attendeeNames:
          type: array
          items:
            type: string
        bannedUserIds:
          type: array
          items:
            type: string
        location:
          type: string
        startTime:
          type: string
          format: date-time
        endTime:
          type: string
          format: date-time
        relatedBroadcastId:
          type: string
          format: uuid
        relatedStreamId:
          type: string
          format: uuid
    JoinContactHistoryRequest:
      type: object
      required:
        - userId
        - userName
      properties:
        userId:
          type: string
          format: uuid
        userName:
          type: string
    HoldCallRequest:
      type: object
      required:
        - isOnHold
      properties:
        isOnHold:
          type: boolean
        holdReason:
          type: string
    TransferCallRequest:
      type: object
      required:
        - newHostUserId
        - newHostUserName
      properties:
        newHostUserId:
          type: string
          format: uuid
        newHostUserName:
          type: string
        transferReason:
          type: string
    
    # Drops Schemas
    DropsResponse:
      type: object
      description: Latest drops response organized by date
      properties:
        userId:
          type: string
          format: uuid
          nullable: true
          description: User ID if drops are for a specific user, null for top artists
        dropsByDate:
          type: array
          description: Drops organized by date
          items:
            $ref: '#/components/schemas/DropsByDate'
        totalCount:
          type: integer
          description: Total number of drop items
    DropsByDate:
      type: object
      description: Drops for a specific date
      properties:
        date:
          type: string
          format: date-time
          description: Date for these drops
        items:
          type: array
          description: Drop items for this date
          items:
            $ref: '#/components/schemas/DropItem'
    DropItem:
      type: object
      description: A single drop item (music project, track, broadcast session, or artist)
      properties:
        id:
          type: string
          description: Item ID
        type:
          type: string
          enum: [music_project, music_track, broadcast_session, artist]
          description: Type of drop item
        title:
          type: string
          description: Item title
        subtitle:
          type: string
          nullable: true
          description: Item subtitle
        date:
          type: string
          format: date-time
          description: Date when item was created/released
        status:
          type: string
          nullable: true
          description: "Status (for broadcast sessions: active, completed, created)"
        imageUrl:
          type: string
          nullable: true
          description: URL for the cover art or thumbnail image
        metadata:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: Additional metadata about the item

    # Playlist Schemas
    PlaylistListItem:
      type: object
      properties:
        id: { type: string, format: uuid }
        ownerId: { type: string, format: uuid }
        name: { type: string }
        nickname: { type: string, nullable: true }
        createdAt: { type: string, format: date-time, nullable: true }
        updatedAt: { type: string, format: date-time, nullable: true }
        genre: { type: string, nullable: true }
        enabled: { type: boolean }
        private: { type: boolean }
        showFollowersOnly: { type: boolean }
        posterUrl: { type: string, nullable: true }
        fromAlbumImage: { type: string, nullable: true }
    PlaylistDetailResponse:
      type: object
      properties:
        playlist: { $ref: '#/components/schemas/PlaylistListItem' }
        tracks:
          type: array
          items: { $ref: '#/components/schemas/PlaylistTrackItem' }
    PlaylistTrackItem:
      type: object
      properties:
        musicTrackId: { type: string }
        musicProjectId: { type: string, nullable: true }
        position: { type: integer }
        trackSnapshot: { type: object, additionalProperties: { type: string }, nullable: true }
    CreatePlaylistRequest:
      type: object
      required: [name]
      properties:
        name: { type: string }
        nickname: { type: string, nullable: true }
        genre: { type: string, nullable: true }
        tags: { type: array, items: { type: string }, nullable: true }
        notes: { type: string, nullable: true }
        metaData: { type: object, additionalProperties: { type: string }, nullable: true }
        releaseDate: { type: string, format: date-time, nullable: true }
        enabled: { type: boolean, nullable: true }
        showFollowersOnly: { type: boolean, nullable: true }
        private: { type: boolean, nullable: true }
        posterUrl: { type: string, nullable: true }
        fromAlbumImage: { type: string, nullable: true }
    UpdatePlaylistRequest:
      type: object
      required: [name]
      properties:
        name: { type: string }
        nickname: { type: string, nullable: true }
        genre: { type: string, nullable: true }
        tags: { type: array, items: { type: string }, nullable: true }
        notes: { type: string, nullable: true }
        metaData: { type: object, additionalProperties: { type: string }, nullable: true }
        releaseDate: { type: string, format: date-time, nullable: true }
        enabled: { type: boolean, nullable: true }
        showFollowersOnly: { type: boolean, nullable: true }
        private: { type: boolean, nullable: true }
        posterUrl: { type: string, nullable: true }
        fromAlbumImage: { type: string, nullable: true }
    AddPlaylistTrackItem:
      type: object
      required: [musicTrackId]
      properties:
        musicTrackId: { type: string }
        musicProjectId: { type: string, nullable: true }
        trackSnapshot: { type: object, additionalProperties: { type: string }, nullable: true }
    
    # Metrics Schemas
    SessionMetricsResponse:
      type: object
      description: Comprehensive dashboard metrics for platform analytics
      properties:
        totalSessions:
          type: integer
          description: Total number of broadcast sessions
          example: 1250
        activeSessions:
          type: integer
          description: Currently active sessions
          example: 42
        completedSessions:
          type: integer
          description: Successfully completed sessions
          example: 1180
        errorSessions:
          type: integer
          description: Sessions that ended with errors
          example: 28
        totalViewers:
          type: integer
          description: Total viewer count across all sessions
          example: 45823
        peakConcurrentViewers:
          type: integer
          description: Highest concurrent viewer count
          example: 1234
        averageViewersPerSession:
          type: number
          format: double
          description: Average viewers per session
          example: 36.6584
        totalWatchTime:
          type: number
          format: double
          description: Total watch time in minutes
          example: 123456.78
        totalRevenue:
          type: number
          format: double
          description: Total revenue in USD
          example: 9876.54
        totalSparks:
          type: integer
          description: Total sparks received
          example: 15432
        totalDiamonds:
          type: integer
          description: Total diamonds received
          example: 3456
        totalReactions:
          type: integer
          description: Total reactions across all sessions
          example: 98765
        sessionStatusBreakdown:
          type: array
          description: Breakdown of sessions by status
          items:
            $ref: '#/components/schemas/SessionStatusCount'
        hourlyStats:
          type: array
          description: Hourly statistics (currently empty, reserved for future use)
          items:
            $ref: '#/components/schemas/HourlyStats'
        topSessions:
          type: array
          description: Top 20 performing sessions
          items:
            $ref: '#/components/schemas/TopSession'
        platformHealth:
          $ref: '#/components/schemas/PlatformHealth'
        lastUpdated:
          type: string
          format: date-time
          description: Timestamp when metrics were last updated
    
    SessionStatusCount:
      type: object
      description: Session count breakdown by status
      properties:
        status:
          type: string
          enum: [active, completed, error, pending, paused, cancelled]
          example: active
        count:
          type: integer
          example: 42
        percentage:
          type: number
          format: double
          description: Percentage of total sessions
          example: 3.36
    
    HourlyStats:
      type: object
      description: Hourly statistics (reserved for future implementation)
      properties:
        hour:
          type: string
          example: "2025-10-21T14:00:00Z"
        sessions:
          type: integer
          example: 25
        viewers:
          type: integer
          example: 450
        revenue:
          type: number
          format: double
          example: 123.45
    
    TopSession:
      type: object
      description: Complete session details with user information and metrics
      properties:
        id:
          type: string
          format: uuid
          example: "123e4567-e89b-12d3-a456-426614174000"
        title:
          type: string
          example: "Epic Gaming Session"
        description:
          type: string
          nullable: true
          example: "An amazing gaming stream"
        hostDisplayName:
          type: string
          nullable: true
          example: "GamerPro123"
        hostUsername:
          type: string
          nullable: true
          example: "gamerpro"
        hostNickname:
          type: string
          nullable: true
          example: "TheGamerPro"
        viewerCount:
          type: integer
          description: Current viewer count
          example: 1234
        maxViewers:
          type: integer
          description: Maximum viewers limit
          example: 5000
        peakViewerCount:
          type: integer
          description: Peak concurrent viewers reached
          example: 2345
        averageViewerCount:
          type: number
          format: double
          description: Average viewers during session
          example: 987.65
        duration:
          type: number
          format: double
          description: Total watch time in minutes
          example: 120.5
        revenue:
          type: number
          format: double
          description: Total revenue in USD
          example: 456.78
        tokens:
          type: integer
          description: Total tokens earned
          example: 789
        sparks:
          type: integer
          description: Total sparks received
          example: 234
        diamonds:
          type: integer
          description: Total diamonds received
          example: 56
        reactions:
          type: integer
          description: Total reactions received
          example: 3456
        watchUrl:
          type: string
          description: URL to watch the session
          example: "https://pubfuse.com/streams/123e4567-e89b-12d3-a456-426614174000/watch"
        sessionIdShort:
          type: string
          description: Short identifier (first 5 chars of UUID)
          example: "123E4"
        streamId:
          type: string
          description: Stream UUID as string
          example: "123e4567-e89b-12d3-a456-426614174000"
        streamKey:
          type: string
          nullable: true
          description: Stream key (UUID without dashes)
          example: "123e4567e89b12d3a456426614174000"
        status:
          type: string
          enum: [pending, active, paused, completed, error, cancelled]
          example: "active"
        visibility:
          type: string
          enum: [public, private]
          example: "public"
        createdAt:
          type: string
          format: date-time
          nullable: true
          example: "2025-10-21T10:30:00Z"
        updatedAt:
          type: string
          format: date-time
          nullable: true
          example: "2025-10-21T12:00:00Z"
        startedAt:
          type: string
          format: date-time
          nullable: true
          example: "2025-10-21T10:35:00Z"
        endedAt:
          type: string
          format: date-time
          nullable: true
          example: "2025-10-21T12:30:00Z"
        userId:
          type: string
          format: uuid
          nullable: true
          description: ID of the user who created the session
          example: "456e7890-e89b-12d3-a456-426614174111"
        userEmail:
          type: string
          nullable: true
          description: User's email address
          example: "user@pubfuse.com"
        userPhone:
          type: string
          nullable: true
          description: User's phone number
          example: "+1234567890"
        userFirstName:
          type: string
          nullable: true
          description: User's first name
          example: "John"
        userLastName:
          type: string
          nullable: true
          description: User's last name
          example: "Doe"
        avatarUrl:
          type: string
          nullable: true
          description: User's avatar URL
          example: "https://pubfuse.com/avatars/user123.jpg"
        isVerified:
          type: boolean
          description: Whether the user is verified
          example: true
        isActive:
          type: boolean
          description: Whether the user account is active
          example: true
        emailVerified:
          type: boolean
          description: Whether the user's email is verified
          example: true
        phoneVerified:
          type: boolean
          description: Whether the user's phone is verified
          example: false
        rtmpUrl:
          type: string
          nullable: true
          description: RTMP ingest URL
          example: "rtmp://pubfuse.com/live/stream123"
        hlsUrl:
          type: string
          nullable: true
          description: HLS playback URL
          example: "https://pubfuse.com/hls/stream123.m3u8"
        webrtcEndpoint:
          type: string
          nullable: true
          description: WebRTC endpoint for real-time streaming
          example: "wss://pubfuse.com/webrtc/session123"
        aiIntent:
          type: string
          nullable: true
          description: AI-detected intent/category
          example: "gaming"
        aiIntentConfidence:
          type: number
          format: double
          nullable: true
          description: AI confidence score (0-1)
          example: 0.95
        moderationStatus:
          type: string
          nullable: true
          enum: [pending, approved, flagged, rejected, auto_approved]
          description: Content moderation status
          example: "approved"
        moderationFlags:
          type: array
          nullable: true
          items:
            type: string
          description: Moderation flags if any
          example: []
        qualityScore:
          type: number
          format: double
          nullable: true
          description: Stream quality score (0-100)
          example: 87.5
        bitrateAvg:
          type: integer
          nullable: true
          description: Average bitrate in kbps
          example: 2500
        resolution:
          type: string
          nullable: true
          description: Stream resolution
          example: "1920x1080"
        fps:
          type: integer
          nullable: true
          description: Frames per second
          example: 30
        errorCount:
          type: integer
          description: Number of errors encountered
          example: 0
        lastError:
          type: string
          nullable: true
          description: Last error message if any
          example: null
        maxGuests:
          type: integer
          description: Maximum number of guests allowed
          example: 5
        currentGuests:
          type: integer
          description: Current number of guests
          example: 2
        guestList:
          type: array
          nullable: true
          items:
            type: string
          description: List of guest user IDs
          example: ["guest1-uuid", "guest2-uuid"]
        subscriptionTierRequired:
          type: string
          nullable: true
          enum: [free, basic, pro, premium]
          description: Required subscription tier to access
          example: null
        accessTokensRequired:
          type: integer
          nullable: true
          description: Number of tokens required to access
          example: null
        isFeatured:
          type: boolean
          description: Whether the session is featured
          example: false
        isPhoneSession:
          type: boolean
          description: Whether this is a phone call session
          example: false
        isMessageSession:
          type: boolean
          description: Whether this is a message session
          example: false
        tags:
          type: array
          nullable: true
          items:
            type: string
          description: Session tags/categories
          example: ["gaming", "fps", "competitive"]
    
    PlatformHealth:
      type: object
      description: Overall platform health status
      properties:
        overallHealth:
          type: string
          enum: [excellent, good, warning, critical]
          description: Overall health status
          example: "good"
        uptime:
          type: number
          format: double
          description: Platform uptime percentage
          example: 99.9
        errorRate:
          type: number
          format: double
          description: Error rate percentage
          example: 2.24
        averageResponseTime:
          type: number
          format: double
          description: Average API response time in milliseconds
          example: 45.6
        activeConnections:
          type: integer
          description: Currently active connections
          example: 42
        databaseStatus:
          type: string
          description: Database health status
          example: "healthy"
        lastIncident:
          type: string
          format: date-time
          nullable: true
          description: Timestamp of last incident
          example: null
    
    # Scheduled Events Schemas
    ScheduledEventResponse:
      type: object
      description: Scheduled event with full details including creator information
      properties:
        id:
          type: string
          format: uuid
          description: Event ID
          example: "123e4567-e89b-12d3-a456-426614174000"
        createdByUserId:
          type: string
          format: uuid
          description: ID of the user who created the event
          example: "123e4567-e89b-12d3-a456-426614174001"
        creator:
          $ref: '#/components/schemas/UserResponse'
          description: Full user object for the event creator
        scheduledStartTime:
          type: string
          format: date-time
          description: Scheduled start time for the event
          example: "2025-11-21T10:00:00Z"
        scheduledEndTime:
          type: string
          format: date-time
          description: Scheduled end time for the event
          example: "2025-11-21T11:00:00Z"
        title:
          type: string
          description: Event title
          example: "Morning Show"
        description:
          type: string
          nullable: true
          description: Event description
          example: "Daily morning show with music and news"
        contentFileUrl:
          type: string
          nullable: true
          format: uri
          description: URL of the media file to play
          example: "https://www.pubfuse.com/userfiles/.../audio.mp3"
        contentType:
          type: string
          enum: [audio, video]
          description: Content type - either "audio" or "video"
          example: "audio"
        broadcastSessionId:
          type: string
          format: uuid
          nullable: true
          description: |
            ID of the broadcast session created when event starts.
            The broadcast session is created using the same unified logic as createBroadcast:
            - Stream is created via StreamService.create()
            - streamId is saved to broadcast_sessions.stream_id
            - All URLs (rtmp_url, hls_url, watch_url) are generated using streamId
            The broadcast_sessions.stream_id matches the livekit_room_name.
          example: "123e4567-e89b-12d3-a456-426614174002"
        status:
          type: string
          enum: [scheduled, active, completed, cancelled]
          description: Current event status
          example: "scheduled"
        playbackMode:
          type: string
          description: Playback mode (usually "video" for scheduled events)
          example: "video"
        currentPlaybackPosition:
          type: integer
          description: Current playback position in seconds
          example: 0
        playbackStartedAt:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when playback actually started
          example: "2025-11-21T10:00:00Z"
        livekitRoomName:
          type: string
          nullable: true
          description: |
            **LiveKit Room Name** - This is the stream_id from the broadcast_sessions table.
            When a scheduled event starts, the scheduler uses unified logic (same as createBroadcast) to:
            - Create a stream via StreamService.create() which generates streamId (UUID)
            - Save streamId to broadcast_sessions.stream_id
            - Set livekit_room_name to match streamId (the UUID string, not "event-{eventId}")
            The livekit_room_name matches the stream_id and is used for LiveKit WebRTC connections.
            Use this value when connecting to LiveKit rooms for scheduled events.
          example: "3af727c6-cd80-414e-83e1-b2ba90d32b8f"
        livekitEgressId:
          type: string
          nullable: true
          description: LiveKit Egress ID (set when Egress starts successfully)
          example: "EG_xxxxx"
        repeatMode:
          type: string
          nullable: true
          enum: [none, until_end, daily, weekly, hourly]
          description: Repeat mode for the event
          example: "hourly"
        repeatUntilEnd:
          type: boolean
          nullable: true
          description: Whether to repeat until end time
          example: false
        onFileCompleted:
          type: string
          nullable: true
          enum: [do_nothing, repeat_until_end, end_session, play_top_videos, play_random_content, play_ads_continuously]
          description: Behavior when file playback completes
          example: "repeat_until_end"
        onFileNotStarted:
          type: string
          nullable: true
          enum: [play_top_videos, play_random_content, play_ads_continuously]
          description: Behavior when file has not started
          example: "play_top_videos"
        adPrerollEnabled:
          type: boolean
          nullable: true
          description: Enable preroll ads at beginning of streams
          example: true
        adIntervalMinutes:
          type: integer
          nullable: true
          description: Interval in minutes between ad breaks
          example: 15
        adCountPerInterval:
          type: integer
          nullable: true
          description: Number of ads to play per interval
          example: 2
        skipAds:
          type: boolean
          nullable: true
          description: Skip ads for paid users (requires enableAdFree on SDK client)
          example: false
        createdAt:
          type: string
          format: date-time
          nullable: true
          description: Event creation timestamp
          example: "2025-11-20T10:00:00Z"
        updatedAt:
          type: string
          format: date-time
          nullable: true
          description: Event last update timestamp
          example: "2025-11-21T10:00:00Z"
    
    ScheduleEventRequest:
      type: object
      description: Request to schedule a new event
      required:
        - scheduledStartTime
        - scheduledEndTime
        - title
      properties:
        scheduledStartTime:
          type: string
          format: date-time
          description: Scheduled start time for the event
          example: "2025-11-21T10:00:00Z"
        scheduledEndTime:
          type: string
          format: date-time
          description: Scheduled end time for the event
          example: "2025-11-21T11:00:00Z"
        title:
          type: string
          description: Event title
          example: "Morning Show"
        description:
          type: string
          nullable: true
          description: Event description
          example: "Daily morning show with music and news"
        contentType:
          type: string
          enum: [audio, video]
          description: Content type - either audio or video, default is video
          example: "audio"
        repeatMode:
          type: string
          enum: [none, until_end, daily, weekly, hourly]
          description: Repeat mode for the event, default is none
          example: "hourly"
        repeatUntilEnd:
          type: boolean
          nullable: true
          description: Whether to repeat until end time, default is false
          example: false
        onFileCompleted:
          type: string
          nullable: true
          enum: [do_nothing, repeat_until_end, end_session, play_top_videos, play_random_content, play_ads_continuously]
          description: Behavior when file playback completes
          example: "repeat_until_end"
        onFileNotStarted:
          type: string
          nullable: true
          enum: [play_top_videos, play_random_content, play_ads_continuously]
          description: Behavior when file has not started
          example: "play_top_videos"
        adPrerollEnabled:
          type: boolean
          nullable: true
          description: Enable preroll ads at beginning of streams
          example: true
        adIntervalMinutes:
          type: integer
          nullable: true
          description: Interval in minutes between ad breaks
          example: 15
        adCountPerInterval:
          type: integer
          nullable: true
          description: Number of ads to play per interval
          example: 2
        skipAds:
          type: boolean
          nullable: true
          description: Skip ads for paid users
          example: false
    
    UpdateEventRequest:
      type: object
      description: Request to update an existing event
      properties:
        scheduledStartTime:
          type: string
          format: date-time
          nullable: true
          description: Updated scheduled start time
          example: "2025-11-21T10:00:00Z"
        scheduledEndTime:
          type: string
          format: date-time
          nullable: true
          description: Updated scheduled end time
          example: "2025-11-21T11:00:00Z"
        title:
          type: string
          nullable: true
          description: Updated event title
          example: "Morning Show"
        description:
          type: string
          nullable: true
          description: Updated event description
          example: "Daily morning show with music and news"
        status:
          type: string
          nullable: true
          enum: [scheduled, active, completed, cancelled]
          description: Updated event status
          example: "active"
        repeatMode:
          type: string
          nullable: true
          enum: [none, until_end, daily, weekly, hourly]
          description: Updated repeat mode
          example: "hourly"
        repeatUntilEnd:
          type: boolean
          nullable: true
          description: Updated repeat until end setting
          example: false
    
    EventBroadcastDebugResponse:
      type: object
      description: Debug information for an event including broadcast and LiveKit status
      properties:
        eventId:
          type: string
          format: uuid
          description: Event ID
          example: "123e4567-e89b-12d3-a456-426614174000"
        eventTitle:
          type: string
          description: Event title
          example: "Morning Show"
        eventDescription:
          type: string
          nullable: true
          description: Event description
          example: "Daily morning show with music and news"
        eventStatus:
          type: string
          enum: [scheduled, active, completed, cancelled]
          description: Current event status
          example: "active"
        scheduledStartTime:
          type: string
          format: date-time
          description: Scheduled start time
          example: "2025-11-21T10:00:00Z"
        scheduledEndTime:
          type: string
          format: date-time
          description: Scheduled end time
          example: "2025-11-21T11:00:00Z"
        playbackStartedAt:
          type: string
          format: date-time
          nullable: true
          description: Timestamp when playback started
          example: "2025-11-21T10:00:00Z"
        broadcastSessionId:
          type: string
          format: uuid
          nullable: true
          description: Broadcast session ID (if created)
          example: "123e4567-e89b-12d3-a456-426614174002"
        livekitRoomName:
          type: string
          nullable: true
          description: LiveKit room name
          example: "event-123e4567-e89b-12d3-a456-426614174000"
        livekitEgressId:
          type: string
          nullable: true
          description: LiveKit Egress ID (if Egress started successfully)
          example: "EG_xxxxx"
        broadcastInfo:
          $ref: '#/components/schemas/BroadcastDebugInfo'
          nullable: true
          description: Broadcast session details (if available)
        isInTopSessions:
          type: boolean
          description: Whether the broadcast session appears in top sessions
          example: true
        hasActiveTracks:
          type: boolean
          nullable: true
          description: Whether the LiveKit room has active audio/video tracks (indicates file is playing)
          example: true
        ingressStatus:
          type: boolean
          nullable: true
          description: Whether the LiveKit Ingress is active/running
          example: true
        nextSchedulerCheckTime:
          type: string
          format: date-time
          description: Next time the scheduler will check this event for file restart
          example: "2025-11-29T22:10:01Z"
        playbackIssue:
          type: string
          nullable: true
          description: Warning message if playback should be active but isn't (e.g., "File should be playing but room has no active tracks")
          example: "File should be playing (event is active, file exists, repeat enabled) but room has no active tracks"
        contentFileUrl:
          type: string
          nullable: true
          format: uri
          description: URL of the content file being played
          example: "https://pubfuse-streaming-bucket.nyc3.cdn.digitaloceanspaces.com/userfiles/.../audio.mp3"
        repeatUntilEnd:
          type: boolean
          description: Whether the file should repeat until the scheduled end time
          example: true
        onFileCompleted:
          type: string
          nullable: true
          description: Behavior when file completes (e.g., "repeat", "stop", "next")
          example: "repeat"
        message:
          type: string
          description: Debug message with status information
          example: "Event is active and broadcasting successfully"
    
    BroadcastDetailsResponse:
      type: object
      description: Comprehensive session metrics for drill-down view
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
        viewerCount:
          type: integer
        peakViewerCount:
          type: integer
        currentViewers:
          type: integer
        averageViewerCount:
          type: number
          format: double
        totalWatchTimeMinutes:
          type: number
          format: double
        totalRevenueUSD:
          type: number
          format: double
        totalSparksReceived:
          type: integer
        totalDiamondsReceived:
          type: integer
        totalTokensEarned:
          type: integer
        totalReactions:
          type: integer
        totalAdViews:
          type: integer
        totalAdImpressions:
          type: integer
        totalPrerollViews:
          type: integer
        totalMidrollViews:
          type: integer
        reactionsPerViewer:
          type: number
          format: double
        revenuePerAdView:
          type: number
          format: double
        revenuePerMinute:
          type: number
          format: double
        createdAt:
          type: string
          format: date-time
          nullable: true
        startedAt:
          type: string
          format: date-time
          nullable: true
        endedAt:
          type: string
          format: date-time
          nullable: true
        hostDisplayName:
          type: string
          nullable: true
        hostUsername:
          type: string
          nullable: true
    
    UserReportResponse:
      type: object
      description: Comprehensive user report for a period
      properties:
        period:
          type: string
          enum: [daily, weekly, monthly, yearly]
        startDate:
          type: string
          format: date-time
        endDate:
          type: string
          format: date-time
        totalStreams:
          type: integer
        liveStreams:
          type: integer
        completedStreams:
          type: integer
        totalViews:
          type: integer
        peakViews:
          type: integer
        minutesStreamed:
          type: number
          format: double
        totalRevenue:
          type: number
          format: double
        totalAdViews:
          type: integer
        totalAdImpressions:
          type: integer
        totalPrerollViews:
          type: integer
        totalMidrollViews:
          type: integer
        totalReactions:
          type: integer
        totalSparks:
          type: integer
        totalDiamonds:
          type: integer
        totalTokens:
          type: integer
        avgViewersPerStream:
          type: number
          format: double
        avgAdsPerStream:
          type: number
          format: double
        followerCount:
          type: integer
        followingCount:
          type: integer
        revenuePerAdView:
          type: number
          format: double
        revenuePerMinute:
          type: number
          format: double
        sessions:
          type: array
          items:
            $ref: '#/components/schemas/SessionSummary'
    
    SessionSummary:
      type: object
      description: Session summary for reports
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
        viewerCount:
          type: integer
        peakViewerCount:
          type: integer
        watchTimeMinutes:
          type: number
          format: double
        revenue:
          type: number
          format: double
        adViews:
          type: integer
        adImpressions:
          type: integer
        reactions:
          type: integer
        createdAt:
          type: string
          format: date-time
          nullable: true
        startedAt:
          type: string
          format: date-time
          nullable: true
        endedAt:
          type: string
          format: date-time
          nullable: true
    
    WhatIfScenarioResponse:
      type: object
      description: What-if scenario calculation results
      properties:
        period:
          type: string
          enum: [daily, weekly, monthly, yearly]
        projectedViews:
          type: integer
        projectedAdViews:
          type: integer
        projectedMinutes:
          type: number
          format: double
        projectedStreams:
          type: integer
        projectedRevenue:
          type: number
          format: double
        projectedRevenueForPeriod:
          type: number
          format: double
        revenuePerAdView:
          type: number
          format: double
        revenuePerMinute:
          type: number
          format: double
        avgViewersPerStream:
          type: number
          format: double
        avgAdViewsPerStream:
          type: number
          format: double
        recommendations:
          type: array
          items:
            type: string
    
    # System Settings Schemas
    SystemSettingsResponse:
      type: object
      description: System-wide settings response including ad frequency, countdown, and preroll settings
      properties:
        general:
          $ref: '#/components/schemas/GeneralSettings'
        security:
          $ref: '#/components/schemas/SecuritySettings'
        notifications:
          $ref: '#/components/schemas/NotificationSettings'
        api:
          $ref: '#/components/schemas/APISettings'
        googleIMA:
          $ref: '#/components/schemas/GoogleIMASettings'
        recording:
          $ref: '#/components/schemas/RecordingSettings'
        ads:
          $ref: '#/components/schemas/AdSettings'
    
    GeneralSettings:
      type: object
      description: General system settings
      properties:
        siteName:
          type: string
          example: "PubFuse"
        siteDescription:
          type: string
          example: "Live streaming platform for developers"
        timezone:
          type: string
          example: "UTC"
        maxSessionsPerUser:
          type: integer
          example: 10
        allowGuestSessions:
          type: boolean
          example: true
    
    SecuritySettings:
      type: object
      description: Security-related settings
      properties:
        sessionTimeoutMinutes:
          type: integer
          example: 60
        maxLoginAttempts:
          type: integer
          example: 5
        passwordMinLength:
          type: integer
          example: 8
        requireTwoFactor:
          type: boolean
          example: false
        enableRateLimiting:
          type: boolean
          example: true
        allowedOrigins:
          type: array
          items:
            type: string
          example: ["https://pubfuse.com", "https://www.pubfuse.com"]
    
    NotificationSettings:
      type: object
      description: Notification settings
      properties:
        notificationEmail:
          type: string
          example: "admin@pubfuse.com"
        emailNewUsers:
          type: boolean
          example: true
        emailNewSessions:
          type: boolean
          example: true
        emailErrors:
          type: boolean
          example: true
        inAppAlerts:
          type: boolean
          example: true
        inAppSounds:
          type: boolean
          example: false
    
    APISettings:
      type: object
      description: API configuration settings
      properties:
        apiVersion:
          type: string
          example: "v1"
        rateLimitPerHour:
          type: integer
          example: 1000
        webhookTimeoutSeconds:
          type: integer
          example: 30
        enableCORS:
          type: boolean
          example: true
        enableWebhooks:
          type: boolean
          example: true
    
    GoogleIMASettings:
      type: object
      description: Google IMA (Interactive Media Ads) settings
      properties:
        enabled:
          type: boolean
          example: false
        publisherId:
          type: string
          nullable: true
          example: null
        appId:
          type: string
          nullable: true
          example: null
        contentSourceId:
          type: string
          nullable: true
          example: null
        videoId:
          type: string
          nullable: true
          example: null
    
    RecordingSettings:
      type: object
      description: Recording and streaming quality settings
      properties:
        recordingMethod:
          type: string
          enum: [room_composite, web_egress]
          example: "room_composite"
        recordingFormat:
          type: string
          enum: [hls, mp4]
          example: "hls"
        hostStreamingQuality:
          type: string
          enum: [720p, 1080p]
          example: "1080p"
        viewerStreamingQuality:
          type: string
          enum: [720p, 1080p]
          example: "720p"
        participantStreamingQuality:
          type: string
          enum: [720p, 1080p]
          example: "720p"
        recordingQuality:
          type: string
          enum: [720p, 1080p]
          example: "1080p"
        mobileWidth:
          type: integer
          example: 390
        mobileHeight:
          type: integer
          example: 644
        videoCodec:
          type: string
          example: "H264_HIGH"
        videoBitrate1080p:
          type: integer
          example: 5000000
        videoBitrate720p:
          type: integer
          example: 3000000
        videoFramerate:
          type: integer
          example: 30
        audioCodec:
          type: string
          example: "OPUS"
        audioBitrate:
          type: integer
          example: 128000
        audioSampleRate:
          type: integer
          example: 48000
        audioChannels:
          type: integer
          example: 2
        layoutStyle:
          type: string
          enum: [grid, speaker, single-speaker, custom]
          example: "single-speaker"
    
    AdSettings:
      type: object
      description: Ad frequency and behavior settings
      properties:
        adFrequencyMinutes:
          type: integer
          description: "Minutes between scheduled ads (default: 15 = ads at :00, :15, :30, :45 on the hour)"
          minimum: 1
          maximum: 60
          example: 15
        adCountdownEnabled:
          type: boolean
          description: "Enable 5-second countdown before ads (default: true)"
          example: true
        adPrerollEnabled:
          type: boolean
          description: "Enable preroll ad when stream starts (default: true)"
          example: true
    
    BroadcastDebugInfo:
      type: object
      description: Debug information for a broadcast session
      properties:
        id:
          type: string
          format: uuid
          description: Broadcast session ID
          example: "123e4567-e89b-12d3-a456-426614174002"
        title:
          type: string
          description: Broadcast title
          example: "Morning Show"
        description:
          type: string
          nullable: true
          description: Broadcast description
          example: "Daily morning show with music and news"
        status:
          type: string
          enum: [pending, active, paused, completed, error, cancelled, private]
          description: Broadcast status
          example: "active"
        visibility:
          type: string
          enum: [public, private]
          description: Broadcast visibility
          example: "public"
        viewerCount:
          type: integer
          description: Current viewer count
          example: 42
        createdAt:
          type: string
          format: date-time
          nullable: true
          description: Broadcast creation timestamp
          example: "2025-11-21T10:00:00Z"
        startedAt:
          type: string
          format: date-time
          nullable: true
          description: Broadcast start timestamp
          example: "2025-11-21T10:00:00Z"
        endedAt:
          type: string
          format: date-time
          nullable: true
          description: Broadcast end timestamp
          example: null
        updatedAt:
          type: string
          format: date-time
          nullable: true
          description: Broadcast last update timestamp
          example: "2025-11-21T10:05:00Z"
    
    # User Interactions Schemas
    CreateUserInteractionRequest:
      type: object
      required:
        - interactionType
        - targetType
        - targetId
      properties:
        interactionType:
          type: string
          enum: [like, gift, spark, diamond, token, reaction, tip, boost, favorite, share, view, comment, follow, subscribe]
          description: Type of interaction
          example: "like"
        targetType:
          type: string
          enum: [broadcast_session, project, track, user, post, comment, event, playlist, creator, message, note, collectable]
          description: Type of target entity
          example: "project"
        targetId:
          type: string
          description: ID of the target entity (can be UUID or String)
          example: "123e4567-e89b-12d3-a456-426614174000"
        amount:
          type: integer
          nullable: true
          description: Amount for monetized interactions (sparks, diamonds, tokens, etc.)
          example: 100
        currency:
          type: string
          enum: [tokens, diamonds, sparks, usd]
          nullable: true
          description: Currency type for monetized interactions
          example: "sparks"
        usdValue:
          type: number
          format: double
          nullable: true
          description: USD value for monetized interactions
          example: 0.50
        metadata:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: Additional flexible metadata
          example:
            source: "mobile_app"
            platform: "ios"
        description:
          type: string
          nullable: true
          description: Description or note for the interaction
          example: "Great project!"
    
    UserInteractionResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Interaction ID
          example: "123e4567-e89b-12d3-a456-426614174000"
        userId:
          type: string
          format: uuid
          description: User who performed the interaction
          example: "123e4567-e89b-12d3-a456-426614174001"
        interactionType:
          type: string
          enum: [like, gift, spark, diamond, token, reaction, tip, boost, favorite, share, view, comment, follow, subscribe]
          description: Type of interaction
          example: "like"
        targetType:
          type: string
          enum: [broadcast_session, project, track, user, post, comment, event, playlist, creator, message, note, collectable]
          description: Type of target entity
          example: "project"
        targetId:
          type: string
          description: ID of the target entity
          example: "123e4567-e89b-12d3-a456-426614174002"
        amount:
          type: integer
          nullable: true
          description: Amount for monetized interactions
          example: 100
        currency:
          type: string
          enum: [tokens, diamonds, sparks, usd]
          nullable: true
          description: Currency type
          example: "sparks"
        usdValue:
          type: number
          format: double
          nullable: true
          description: USD value
          example: 0.50
        metadata:
          type: object
          additionalProperties:
            type: string
          nullable: true
          description: Additional metadata
        description:
          type: string
          nullable: true
          description: Description or note
        createdAt:
          type: string
          format: date-time
          nullable: true
          description: Creation timestamp
          example: "2025-11-21T10:00:00Z"
        updatedAt:
          type: string
          format: date-time
          nullable: true
          description: Last update timestamp
          example: "2025-11-21T10:00:00Z"
    
    InteractionStatsResponse:
      type: object
      description: Aggregated statistics for interactions on a target entity
      properties:
        targetType:
          type: string
          enum: [broadcast_session, project, track, user, post, comment, event, playlist, creator, message, note, collectable]
          description: Type of target entity
          example: "project"
        targetId:
          type: string
          description: ID of the target entity
          example: "123e4567-e89b-12d3-a456-426614174002"
        totalLikes:
          type: integer
          description: Total number of likes
          example: 42
        totalGifts:
          type: integer
          description: Total number of gifts
          example: 5
        totalSparks:
          type: integer
          description: Total number of sparks received
          example: 1000
        totalDiamonds:
          type: integer
          description: Total number of diamonds received
          example: 50
        totalTokens:
          type: integer
          description: Total number of tokens received
          example: 200
        totalReactions:
          type: integer
          description: Total number of reactions
          example: 15
        totalInteractions:
          type: integer
          description: Total number of all interactions
          example: 1312
        interactionsByType:
          type: object
          additionalProperties:
            type: integer
          description: Count of interactions grouped by type
          example:
            like: 42
            spark: 1000
            diamond: 50
            reaction: 15

    # Pubfuse Music Helpers Schemas
    PubfuseMusicItemInfoResponse:
      type: object
      description: Combined view of a pubfu.se music item with its linked pubfuse.com user and interaction stats.
      properties:
        targetType:
          type: string
          enum: [broadcast_session, project, track, user, post, comment, event, playlist, creator, message, note, collectable]
          description: Type of target entity (project, track, creator, etc.)
        targetId:
          type: string
          description: ID of the music item (pubfu.se)
        pubfuseMusicUserId:
          type: string
          nullable: true
          description: Linked pubfu.se music user ID (created_by) that owns this music item.
        user:
          $ref: '#/components/schemas/UserResponse'
        likeCount:
          type: integer
          description: Total number of like interactions for this item.
          example: 12
        viewCount:
          type: integer
          description: Total number of view interactions for this item.
          example: 123
        project:
          $ref: '#/components/schemas/MusicProject'
          nullable: true
          description: Parent project/album for the track when applicable.

    MusicProject:
      type: object
      description: Lightweight representation of a pubfu.se music project/album.
      properties:
        id:
          type: string
          description: Project ID from pubfu.se
        name:
          type: string
          nullable: true
        summaryShort:
          type: string
          nullable: true
        summaryLong:
          type: string
          nullable: true
        coverArtAlbumFront:
          type: string
          nullable: true
        primaryArtistName:
          type: string
          nullable: true
        creatorId:
          type: string
          nullable: true
        createdAt:
          type: string
          nullable: true
          description: Creation timestamp as provided by pubfu.se
        updatedAt:
          type: string
          nullable: true
          description: Last update timestamp as provided by pubfu.se
        releaseDateTimestamp:
          type: string
          nullable: true
          description: Release date timestamp (stringified Unix timestamp or ISO date)
        enabled:
          type: boolean
          description: Whether the project is enabled/visible in pubfu.se