false, 'data' => null, 'error' => null]; try { // Get action from query parameter $action = isset($_GET['action']) ? $_GET['action'] : null; $method = $_SERVER['REQUEST_METHOD']; // Get authenticated user (supports both session and JWT) $userId = null; // Try JWT authentication first $authHeader = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) ? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] : null); if ($authHeader && preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) { $token = $matches[1]; $tokenData = VAuth::verifyToken($token); if ($tokenData && isset($tokenData['user_id'])) { $userId = $tokenData['user_id']; } } // Fall back to session authentication if (!$userId && isset($_SESSION['USER_ID'])) { $userId = $_SESSION['USER_ID']; } elseif (!$userId && isset($_SESSION['usr_id'])) { $userId = $_SESSION['usr_id']; } // Route based on method and action switch ($method) { case 'GET': if (isset($_GET['id'])) { // Get single video handleGetVideo($_GET['id'], $userId); } elseif ($action === 'search') { // Search videos handleSearchVideos($userId); } else { // List videos handleListVideos($userId); } break; case 'POST': if (!$userId) { throw new Exception('Authentication required', 401); } switch ($action) { case 'create': case 'upload': handleCreateVideo($userId); break; case 'like': handleLikeVideo($userId); break; case 'view': handleIncrementView($userId); break; case 'watch_later': handleWatchLater($userId); break; default: throw new Exception('Invalid action', 400); } break; case 'PUT': if (!$userId) { throw new Exception('Authentication required', 401); } handleUpdateVideo($userId); break; case 'DELETE': if (!$userId) { throw new Exception('Authentication required', 401); } handleDeleteVideo($userId); break; default: throw new Exception('Method not allowed', 405); } } catch (Exception $e) { http_response_code($e->getCode() >= 400 && $e->getCode() < 600 ? $e->getCode() : 500); $response['error'] = $e->getMessage(); echo json_encode($response); exit; } /** * List videos with pagination and filters */ function handleListVideos($userId) { global $class_database, $response; // Get pagination parameters $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; $limit = isset($_GET['limit']) ? min(100, max(1, (int)$_GET['limit'])) : 20; $offset = ($page - 1) * $limit; // Get filter parameters $category = isset($_GET['category']) ? VSecurity::sanitize($_GET['category']) : null; $sort = isset($_GET['sort']) ? $_GET['sort'] : 'recent'; $channelId = isset($_GET['channel_id']) ? (int)$_GET['channel_id'] : null; // Build WHERE clause $where = ["v.approved = 1"]; $params = []; if ($category) { $where[] = "v.file_category = ?"; $params[] = $category; } if ($channelId) { $where[] = "v.usr_id = ?"; $params[] = $channelId; } // Add privacy filter (only show public videos unless it's the owner) if ($userId) { $where[] = "(v.privacy = 'public' OR v.usr_id = ?)"; $params[] = $userId; } else { $where[] = "v.privacy = 'public'"; } $whereClause = implode(' AND ', $where); // Build ORDER BY clause $orderBy = match($sort) { 'popular' => 'v.file_views DESC', 'featured' => 'v.featured DESC, v.file_views DESC', 'recent' => 'v.upload_date DESC', 'oldest' => 'v.upload_date ASC', 'title' => 'v.file_title ASC', default => 'v.upload_date DESC' }; // Get total count $countSql = "SELECT COUNT(*) FROM db_videofiles v WHERE $whereClause"; $total = (int)$class_database->singleFieldValue($countSql, $params); // Get videos $sql = "SELECT v.file_key, v.file_title, v.file_description, v.file_name, v.file_duration, v.file_views, v.privacy, v.upload_date, v.thumbnail, v.featured, v.file_category, u.usr_id, u.usr_user, u.usr_dname, (SELECT COUNT(*) FROM db_likes WHERE file_key = v.file_key AND like_type = 'like') as like_count, (SELECT COUNT(*) FROM db_comments WHERE file_key = v.file_key) as comment_count FROM db_videofiles v LEFT JOIN db_users u ON v.usr_id = u.usr_id WHERE $whereClause ORDER BY $orderBy LIMIT ? OFFSET ?"; $params[] = $limit; $params[] = $offset; $videos = $class_database->execute($sql, $params); // Format response $response['success'] = true; $response['data'] = [ 'videos' => $videos ? $videos->GetArray() : [], 'pagination' => [ 'page' => $page, 'limit' => $limit, 'total' => $total, 'pages' => ceil($total / $limit) ] ]; echo json_encode($response); } /** * Get single video details */ function handleGetVideo($fileKey, $userId) { global $class_database, $response; $sql = "SELECT v.*, u.usr_id, u.usr_user, u.usr_dname, u.usr_avatar, (SELECT COUNT(*) FROM db_likes WHERE file_key = v.file_key AND like_type = 'like') as like_count, (SELECT COUNT(*) FROM db_likes WHERE file_key = v.file_key AND like_type = 'dislike') as dislike_count, (SELECT COUNT(*) FROM db_comments WHERE file_key = v.file_key) as comment_count, (SELECT COUNT(*) FROM db_subscriptions WHERE channel_id = v.usr_id) as subscriber_count FROM db_videofiles v LEFT JOIN db_users u ON v.usr_id = u.usr_id WHERE v.file_key = ?"; $result = $class_database->execute($sql, [$fileKey]); if (!$result || $result->RecordCount() === 0) { throw new Exception('Video not found', 404); } $video = $result->fields; // Check privacy if ($video['privacy'] !== 'public' && (!$userId || $userId != $video['usr_id'])) { throw new Exception('Video not available', 403); } // Check if user liked/disliked if ($userId) { $likeSql = "SELECT like_type FROM db_likes WHERE file_key = ? AND usr_id = ?"; $likeResult = $class_database->execute($likeSql, [$fileKey, $userId]); $video['user_like_status'] = $likeResult && $likeResult->RecordCount() > 0 ? $likeResult->fields['like_type'] : null; // Check if subscribed to channel $subSql = "SELECT 1 FROM db_subscriptions WHERE usr_id = ? AND channel_id = ?"; $subResult = $class_database->execute($subSql, [$userId, $video['usr_id']]); $video['user_subscribed'] = $subResult && $subResult->RecordCount() > 0; } $response['success'] = true; $response['data'] = $video; echo json_encode($response); } /** * Search videos */ function handleSearchVideos($userId) { global $class_database, $response; $query = isset($_GET['q']) ? VSecurity::sanitize($_GET['q']) : ''; if (strlen($query) < 2) { throw new Exception('Search query must be at least 2 characters', 400); } $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; $limit = isset($_GET['limit']) ? min(100, max(1, (int)$_GET['limit'])) : 20; $offset = ($page - 1) * $limit; $searchTerm = '%' . $query . '%'; $where = "(v.file_title LIKE ? OR v.file_description LIKE ? OR v.file_tags LIKE ?)"; if ($userId) { $where .= " AND (v.privacy = 'public' OR v.usr_id = ?)"; $params = [$searchTerm, $searchTerm, $searchTerm, $userId]; } else { $where .= " AND v.privacy = 'public'"; $params = [$searchTerm, $searchTerm, $searchTerm]; } // Get total count $countSql = "SELECT COUNT(*) FROM db_videofiles v WHERE v.approved = 1 AND $where"; $total = (int)$class_database->singleFieldValue($countSql, $params); // Get results $sql = "SELECT v.file_key, v.file_title, v.file_description, v.file_duration, v.file_views, v.upload_date, v.thumbnail, u.usr_id, u.usr_user, u.usr_dname FROM db_videofiles v LEFT JOIN db_users u ON v.usr_id = u.usr_id WHERE v.approved = 1 AND $where ORDER BY v.file_views DESC, v.upload_date DESC LIMIT ? OFFSET ?"; $params[] = $limit; $params[] = $offset; $videos = $class_database->execute($sql, $params); $response['success'] = true; $response['data'] = [ 'videos' => $videos ? $videos->GetArray() : [], 'query' => $query, 'pagination' => [ 'page' => $page, 'limit' => $limit, 'total' => $total, 'pages' => ceil($total / $limit) ] ]; echo json_encode($response); } /** * Create/upload new video */ function handleCreateVideo($userId) { global $class_database, $response; // Get JSON input $input = json_decode(file_get_contents('php://input'), true); if (!$input) { throw new Exception('Invalid JSON input', 400); } // Validate required fields $title = isset($input['title']) ? VSecurity::sanitize($input['title']) : null; $description = isset($input['description']) ? VSecurity::sanitize($input['description']) : ''; if (!$title) { throw new Exception('Title is required', 400); } // Generate file key $fileKey = rand(100000, 999999); // Get optional fields $privacy = isset($input['privacy']) ? $input['privacy'] : 'public'; $category = isset($input['category']) ? VSecurity::sanitize($input['category']) : null; $tags = isset($input['tags']) ? VSecurity::sanitize($input['tags']) : null; // Insert video record $sql = "INSERT INTO db_videofiles (usr_id, file_key, file_type, file_title, file_description, privacy, file_category, file_tags, upload_date, approved, file_views) VALUES (?, ?, 'video', ?, ?, ?, ?, ?, NOW(), 1, 0)"; $result = $class_database->execute($sql, [ $userId, $fileKey, $title, $description, $privacy, $category, $tags ]); if (!$result) { throw new Exception('Failed to create video', 500); } $response['success'] = true; $response['data'] = [ 'file_key' => $fileKey, 'message' => 'Video created successfully' ]; echo json_encode($response); } /** * Update video details */ function handleUpdateVideo($userId) { global $class_database, $response; $fileKey = isset($_GET['id']) ? $_GET['id'] : null; if (!$fileKey) { throw new Exception('Video ID is required', 400); } // Verify ownership $checkSql = "SELECT usr_id FROM db_videofiles WHERE file_key = ?"; $checkResult = $class_database->execute($checkSql, [$fileKey]); if (!$checkResult || $checkResult->RecordCount() === 0) { throw new Exception('Video not found', 404); } if ($checkResult->fields['usr_id'] != $userId) { throw new Exception('You do not have permission to edit this video', 403); } // Get JSON input $input = json_decode(file_get_contents('php://input'), true); if (!$input) { throw new Exception('Invalid JSON input', 400); } // Build update query dynamically $updates = []; $params = []; $allowedFields = ['file_title', 'file_description', 'privacy', 'file_category', 'file_tags', 'thumbnail']; foreach ($allowedFields as $field) { if (isset($input[$field])) { $updates[] = "$field = ?"; $params[] = VSecurity::sanitize($input[$field]); } } if (empty($updates)) { throw new Exception('No fields to update', 400); } $params[] = $fileKey; $sql = "UPDATE db_videofiles SET " . implode(', ', $updates) . " WHERE file_key = ?"; $result = $class_database->execute($sql, $params); if (!$result) { throw new Exception('Failed to update video', 500); } $response['success'] = true; $response['data'] = ['message' => 'Video updated successfully']; echo json_encode($response); } /** * Delete video */ function handleDeleteVideo($userId) { global $class_database, $response; $fileKey = isset($_GET['id']) ? $_GET['id'] : null; if (!$fileKey) { throw new Exception('Video ID is required', 400); } // Verify ownership $checkSql = "SELECT usr_id, file_name FROM db_videofiles WHERE file_key = ?"; $checkResult = $class_database->execute($checkSql, [$fileKey]); if (!$checkResult || $checkResult->RecordCount() === 0) { throw new Exception('Video not found', 404); } if ($checkResult->fields['usr_id'] != $userId) { throw new Exception('You do not have permission to delete this video', 403); } // Delete video record $sql = "DELETE FROM db_videofiles WHERE file_key = ?"; $result = $class_database->execute($sql, [$fileKey]); if (!$result) { throw new Exception('Failed to delete video', 500); } // TODO: Delete associated files from storage $response['success'] = true; $response['data'] = ['message' => 'Video deleted successfully']; echo json_encode($response); } /** * Like/unlike video */ function handleLikeVideo($userId) { global $class_database, $response; $input = json_decode(file_get_contents('php://input'), true); $fileKey = isset($input['file_key']) ? $input['file_key'] : null; $likeType = isset($input['like_type']) ? $input['like_type'] : 'like'; // 'like' or 'dislike' if (!$fileKey) { throw new Exception('Video ID is required', 400); } // Check if already liked $checkSql = "SELECT like_id, like_type FROM db_likes WHERE file_key = ? AND usr_id = ?"; $existing = $class_database->execute($checkSql, [$fileKey, $userId]); if ($existing && $existing->RecordCount() > 0) { if ($existing->fields['like_type'] === $likeType) { // Remove like/dislike $sql = "DELETE FROM db_likes WHERE file_key = ? AND usr_id = ?"; $class_database->execute($sql, [$fileKey, $userId]); $action = 'removed'; } else { // Change like to dislike or vice versa $sql = "UPDATE db_likes SET like_type = ? WHERE file_key = ? AND usr_id = ?"; $class_database->execute($sql, [$likeType, $fileKey, $userId]); $action = 'updated'; } } else { // Add new like/dislike $sql = "INSERT INTO db_likes (file_key, usr_id, like_type, like_date) VALUES (?, ?, ?, NOW())"; $class_database->execute($sql, [$fileKey, $userId, $likeType]); $action = 'added'; } // Get updated counts $countSql = "SELECT SUM(CASE WHEN like_type = 'like' THEN 1 ELSE 0 END) as like_count, SUM(CASE WHEN like_type = 'dislike' THEN 1 ELSE 0 END) as dislike_count FROM db_likes WHERE file_key = ?"; $counts = $class_database->execute($countSql, [$fileKey]); $response['success'] = true; $response['data'] = [ 'action' => $action, 'like_count' => $counts->fields['like_count'] ?? 0, 'dislike_count' => $counts->fields['dislike_count'] ?? 0 ]; echo json_encode($response); } /** * Increment view count */ function handleIncrementView($userId) { global $class_database, $response; $input = json_decode(file_get_contents('php://input'), true); $fileKey = isset($input['file_key']) ? $input['file_key'] : null; if (!$fileKey) { throw new Exception('Video ID is required', 400); } // Update view count $sql = "UPDATE db_videofiles SET file_views = file_views + 1 WHERE file_key = ?"; $class_database->execute($sql, [$fileKey]); // Log view in activity table if it exists $activitySql = "INSERT INTO db_user_activity (usr_id, activity_type, file_key, activity_date) VALUES (?, 'view', ?, NOW())"; $class_database->execute($activitySql, [$userId, $fileKey]); $response['success'] = true; $response['data'] = ['message' => 'View recorded']; echo json_encode($response); } /** * Add/remove video from watch later */ function handleWatchLater($userId) { global $class_database, $response; $input = json_decode(file_get_contents('php://input'), true); $fileKey = isset($input['file_key']) ? $input['file_key'] : null; if (!$fileKey) { throw new Exception('Video ID is required', 400); } // Check if already in watch later $checkSql = "SELECT 1 FROM db_watchlater WHERE file_key = ? AND usr_id = ?"; $existing = $class_database->execute($checkSql, [$fileKey, $userId]); if ($existing && $existing->RecordCount() > 0) { // Remove from watch later $sql = "DELETE FROM db_watchlater WHERE file_key = ? AND usr_id = ?"; $class_database->execute($sql, [$fileKey, $userId]); $action = 'removed'; } else { // Add to watch later $sql = "INSERT INTO db_watchlater (file_key, usr_id, added_date) VALUES (?, ?, NOW())"; $class_database->execute($sql, [$fileKey, $userId]); $action = 'added'; } $response['success'] = true; $response['data'] = ['action' => $action]; echo json_encode($response); }