db = VDatabase::getInstance(); $this->logger = VLogger::getInstance(); $this->rbac = VRBAC::getInstance(); $this->rateLimiter = VSecurity::getInstance(); } /** * Handle API request * @param string $method HTTP method * @param string $endpoint API endpoint * @param array $data Request data * @param array $headers Request headers * @return array API response */ public function handleRequest($method, $endpoint, $data = [], $headers = []) { try { // Set CORS headers $this->setCORSHeaders(); // Handle preflight requests if ($method === 'OPTIONS') { return $this->response(['message' => 'OK'], self::SUCCESS); } // Rate limiting $clientId = $this->getClientIdentifier($headers); if (!$this->checkRateLimit($clientId, $endpoint)) { return $this->response(['error' => 'Rate limit exceeded'], self::RATE_LIMITED); } // Authentication $user = $this->authenticateRequest($headers); // Route request $response = $this->routeRequest($method, $endpoint, $data, $user); // Log API request $this->logAPIRequest($method, $endpoint, $user['id'] ?? null, $response['status']); return $response; } catch (Exception $e) { $this->logger->error('API request failed', [ 'method' => $method, 'endpoint' => $endpoint, 'error' => $e->getMessage() ]); return $this->response(['error' => 'Internal server error'], self::SERVER_ERROR); } } /** * Route API request to appropriate handler * @param string $method HTTP method * @param string $endpoint API endpoint * @param array $data Request data * @param array|null $user Authenticated user * @return array API response */ private function routeRequest($method, $endpoint, $data, $user) { $parts = explode('/', trim($endpoint, '/')); $resource = $parts[0] ?? ''; $id = $parts[1] ?? null; $action = $parts[2] ?? null; switch ($resource) { case 'auth': return $this->handleAuth($method, $id, $data); case 'videos': return $this->handleVideos($method, $id, $action, $data, $user); case 'users': return $this->handleUsers($method, $id, $action, $data, $user); case 'live': return $this->handleLiveStreams($method, $id, $action, $data, $user); case 'search': return $this->handleSearch($method, $data, $user); case 'upload': return $this->handleUpload($method, $data, $user); case 'analytics': return $this->handleAnalytics($method, $id, $action, $data, $user); default: return $this->response(['error' => 'Endpoint not found'], self::NOT_FOUND); } } /** * Handle authentication endpoints * @param string $method HTTP method * @param string $action Action * @param array $data Request data * @return array API response */ private function handleAuth($method, $action, $data) { switch ($action) { case 'login': if ($method !== 'POST') { return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED); } return $this->login($data); case 'register': if ($method !== 'POST') { return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED); } return $this->register($data); case 'refresh': if ($method !== 'POST') { return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED); } return $this->refreshToken($data); case 'logout': if ($method !== 'POST') { return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED); } return $this->logout($data); default: return $this->response(['error' => 'Auth action not found'], self::NOT_FOUND); } } /** * Handle video endpoints * @param string $method HTTP method * @param string $id Video ID * @param string $action Action * @param array $data Request data * @param array|null $user Authenticated user * @return array API response */ private function handleVideos($method, $id, $action, $data, $user) { switch ($method) { case 'GET': if ($id) { if ($action === 'comments') { return $this->getVideoComments($id, $data); } elseif ($action === 'related') { return $this->getRelatedVideos($id, $data); } else { return $this->getVideo($id, $user); } } else { return $this->getVideos($data, $user); } case 'POST': if ($id && $action === 'like') { return $this->likeVideo($id, $user); } elseif ($id && $action === 'comment') { return $this->commentVideo($id, $data, $user); } else { return $this->createVideo($data, $user); } case 'PUT': if ($id) { return $this->updateVideo($id, $data, $user); } break; case 'DELETE': if ($id) { return $this->deleteVideo($id, $user); } break; } return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED); } /** * Handle user endpoints * @param string $method HTTP method * @param string $id User ID * @param string $action Action * @param array $data Request data * @param array|null $user Authenticated user * @return array API response */ private function handleUsers($method, $id, $action, $data, $user) { switch ($method) { case 'GET': if ($id) { if ($action === 'videos') { return $this->getUserVideos($id, $data); } elseif ($action === 'followers') { return $this->getUserFollowers($id, $data); } elseif ($action === 'following') { return $this->getUserFollowing($id, $data); } else { return $this->getUser($id, $user); } } else { return $this->getUsers($data, $user); } case 'POST': if ($id && $action === 'follow') { return $this->followUser($id, $user); } break; case 'PUT': if ($id === 'me' || ($user && $id == $user['id'])) { return $this->updateProfile($data, $user); } break; case 'DELETE': if ($id && $action === 'follow') { return $this->unfollowUser($id, $user); } break; } return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED); } /** * Handle live stream endpoints * @param string $method HTTP method * @param string $id Stream ID * @param string $action Action * @param array $data Request data * @param array|null $user Authenticated user * @return array API response */ private function handleLiveStreams($method, $id, $action, $data, $user) { if (!$user) { return $this->response(['error' => 'Authentication required'], self::UNAUTHORIZED); } switch ($method) { case 'GET': if ($id) { return $this->getLiveStream($id, $user); } else { return $this->getLiveStreams($data, $user); } case 'POST': if ($id && $action === 'start') { return $this->startLiveStream($id, $user); } elseif ($id && $action === 'stop') { return $this->stopLiveStream($id, $user); } else { return $this->createLiveStream($data, $user); } case 'PUT': if ($id) { return $this->updateLiveStream($id, $data, $user); } break; case 'DELETE': if ($id) { return $this->deleteLiveStream($id, $user); } break; } return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED); } /** * Authenticate API request * @param array $headers Request headers * @return array|null User data or null if not authenticated */ private function authenticateRequest($headers) { $authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? ''; if (empty($authHeader)) { return null; } // Support Bearer token format if (strpos($authHeader, 'Bearer ') === 0) { $token = substr($authHeader, 7); return $this->validateJWTToken($token); } // Support API key format if (strpos($authHeader, 'ApiKey ') === 0) { $apiKey = substr($authHeader, 7); return $this->validateAPIKey($apiKey); } return null; } /** * Validate JWT token * @param string $token JWT token * @return array|null User data or null if invalid */ private function validateJWTToken($token) { try { // Simple JWT validation (in production, use a proper JWT library) $parts = explode('.', $token); if (count($parts) !== 3) { return null; } $payload = json_decode(base64_decode($parts[1]), true); if (!$payload || !isset($payload['user_id']) || $payload['exp'] < time()) { return null; } // Get user from database $query = "SELECT usr_id, usr_user, usr_email, usr_dname FROM db_accountuser WHERE usr_id = ? AND usr_status = 'active'"; $result = $this->db->doQuery($query, [$payload['user_id']]); $user = $this->db->doFetch($result); if ($user) { return [ 'id' => $user['usr_id'], 'username' => $user['usr_user'], 'email' => $user['usr_email'], 'display_name' => $user['usr_dname'] ]; } return null; } catch (Exception $e) { $this->logger->error('JWT validation failed', ['error' => $e->getMessage()]); return null; } } /** * Validate API key * @param string $apiKey API key * @return array|null User data or null if invalid */ private function validateAPIKey($apiKey) { try { $query = "SELECT ak.user_id, ak.permissions, au.usr_user, au.usr_email, au.usr_dname FROM db_api_keys ak JOIN db_accountuser au ON ak.user_id = au.usr_id WHERE ak.api_key = ? AND ak.status = 'active' AND ak.expires_at > NOW()"; $result = $this->db->doQuery($query, [$apiKey]); $keyData = $this->db->doFetch($result); if ($keyData) { return [ 'id' => $keyData['user_id'], 'username' => $keyData['usr_user'], 'email' => $keyData['usr_email'], 'display_name' => $keyData['usr_dname'], 'api_permissions' => json_decode($keyData['permissions'], true) ?: [] ]; } return null; } catch (Exception $e) { $this->logger->error('API key validation failed', ['error' => $e->getMessage()]); return null; } } /** * Generate JWT token * @param array $user User data * @return string JWT token */ private function generateJWTToken($user) { $header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']); $payload = json_encode([ 'user_id' => $user['id'], 'username' => $user['username'], 'iat' => time(), 'exp' => time() + (24 * 60 * 60) // 24 hours ]); $headerEncoded = base64_encode($header); $payloadEncoded = base64_encode($payload); $signature = hash_hmac('sha256', $headerEncoded . '.' . $payloadEncoded, 'your-secret-key', true); $signatureEncoded = base64_encode($signature); return $headerEncoded . '.' . $payloadEncoded . '.' . $signatureEncoded; } /** * Login user * @param array $data Login data * @return array API response */ private function login($data) { $username = $data['username'] ?? ''; $password = $data['password'] ?? ''; if (empty($username) || empty($password)) { return $this->response(['error' => 'Username and password required'], self::BAD_REQUEST); } // Validate credentials $query = "SELECT usr_id, usr_user, usr_email, usr_dname, usr_password FROM db_accountuser WHERE (usr_user = ? OR usr_email = ?) AND usr_status = 'active'"; $result = $this->db->doQuery($query, [$username, $username]); $user = $this->db->doFetch($result); if (!$user || !password_verify($password, $user['usr_password'])) { return $this->response(['error' => 'Invalid credentials'], self::UNAUTHORIZED); } // Generate token $userData = [ 'id' => $user['usr_id'], 'username' => $user['usr_user'], 'email' => $user['usr_email'], 'display_name' => $user['usr_dname'] ]; $token = $this->generateJWTToken($userData); // Update last login $this->db->doUpdate('db_accountuser', 'usr_id', [ 'usr_lastlogin' => date('Y-m-d H:i:s') ], $user['usr_id']); return $this->response([ 'token' => $token, 'user' => $userData, 'expires_in' => 86400 // 24 hours ], self::SUCCESS); } /** * Get videos * @param array $params Query parameters * @param array|null $user Authenticated user * @return array API response */ private function getVideos($params, $user) { $page = max(1, (int)($params['page'] ?? 1)); $limit = min(50, max(1, (int)($params['limit'] ?? 20))); $offset = ($page - 1) * $limit; $category = $params['category'] ?? ''; $sort = $params['sort'] ?? 'recent'; $where = ["vf.file_type = 'video'", "vf.privacy = 'public'"]; $queryParams = []; if ($category) { $where[] = "vf.file_category = ?"; $queryParams[] = $category; } $orderBy = match($sort) { 'popular' => 'vf.file_views DESC', 'recent' => 'vf.upload_date DESC', 'rating' => 'vf.file_rating DESC', default => 'vf.upload_date DESC' }; $whereClause = implode(' AND ', $where); $query = "SELECT vf.file_key, vf.file_title, vf.file_description, vf.file_views, vf.file_rating, vf.upload_date, vf.file_duration, vf.file_size, au.usr_user, au.usr_dname FROM db_videofiles vf JOIN db_accountuser au ON vf.usr_id = au.usr_id WHERE {$whereClause} ORDER BY {$orderBy} LIMIT {$limit} OFFSET {$offset}"; $result = $this->db->doQuery($query, $queryParams); $videos = []; while ($row = $this->db->doFetch($result)) { $videos[] = [ 'id' => $row['file_key'], 'title' => $row['file_title'], 'description' => $row['file_description'], 'views' => (int)$row['file_views'], 'rating' => (float)$row['file_rating'], 'duration' => (int)$row['file_duration'], 'size' => (int)$row['file_size'], 'uploaded_at' => $row['upload_date'], 'uploader' => [ 'username' => $row['usr_user'], 'display_name' => $row['usr_dname'] ], 'thumbnail_url' => "/thumbnails/{$row['file_key']}_medium.jpg", 'video_url' => "/watch/{$row['file_key']}" ]; } return $this->response([ 'videos' => $videos, 'pagination' => [ 'page' => $page, 'limit' => $limit, 'total' => $this->getVideoCount($where, $queryParams) ] ], self::SUCCESS); } /** * Create API response * @param array $data Response data * @param int $status HTTP status code * @return array API response */ private function response($data, $status = self::SUCCESS) { return [ 'status' => $status, 'data' => $data, 'timestamp' => time(), 'version' => $this->version ]; } /** * Set CORS headers */ private function setCORSHeaders() { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With'); header('Access-Control-Max-Age: 86400'); } /** * Check rate limit * @param string $clientId Client identifier * @param string $endpoint Endpoint * @return bool True if within limits */ private function checkRateLimit($clientId, $endpoint) { // Different limits for different endpoints $limits = [ 'auth' => ['requests' => 10, 'window' => 300], // 10 requests per 5 minutes 'upload' => ['requests' => 5, 'window' => 3600], // 5 uploads per hour 'default' => ['requests' => 100, 'window' => 3600] // 100 requests per hour ]; $endpointType = explode('/', $endpoint)[0] ?? 'default'; $limit = $limits[$endpointType] ?? $limits['default']; return $this->rateLimiter->checkRateLimit( "api_{$clientId}_{$endpointType}", $limit['requests'], $limit['window'], "api_{$endpointType}" ); } /** * Get client identifier * @param array $headers Request headers * @return string Client identifier */ private function getClientIdentifier($headers) { // Use API key if available, otherwise IP address $authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? ''; if (strpos($authHeader, 'ApiKey ') === 0) { return 'key_' . substr($authHeader, 7, 10); } return 'ip_' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'); } /** * Log API request * @param string $method HTTP method * @param string $endpoint Endpoint * @param int|null $userId User ID * @param int $status Response status */ private function logAPIRequest($method, $endpoint, $userId, $status) { try { $logData = [ 'method' => $method, 'endpoint' => $endpoint, 'user_id' => $userId, 'status' => $status, 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown', 'created_at' => date('Y-m-d H:i:s') ]; $this->db->doInsert('db_api_logs', $logData); } catch (Exception $e) { $this->logger->error('Failed to log API request', [ 'error' => $e->getMessage() ]); } } /** * Get video count for pagination * @param array $where Where conditions * @param array $params Query parameters * @return int Total count */ private function getVideoCount($where, $params) { $whereClause = implode(' AND ', $where); $query = "SELECT COUNT(*) as total FROM db_videofiles vf WHERE {$whereClause}"; $result = $this->db->doQuery($query, $params); $row = $this->db->doFetch($result); return (int)($row['total'] ?? 0); } }