false, 'data' => null, 'error' => null]; try { // Get action from query parameter $action = isset($_GET['action']) ? $_GET['action'] : 'profile'; $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': switch ($action) { case 'profile': case 'me': handleGetProfile($userId); break; case 'stats': handleGetStats($userId); break; case 'videos': handleGetUserVideos($userId); break; case 'subscriptions': handleGetSubscriptions($userId); break; case 'subscribers': handleGetSubscribers($userId); break; default: // Get another user's public profile $targetUserId = isset($_GET['id']) ? (int)$_GET['id'] : null; if ($targetUserId) { handleGetPublicProfile($targetUserId, $userId); } else { handleGetProfile($userId); } } break; case 'POST': if (!$userId) { throw new Exception('Authentication required', 401); } switch ($action) { case 'avatar': handleUploadAvatar($userId); break; case 'subscribe': handleSubscribe($userId); break; case 'unsubscribe': handleUnsubscribe($userId); break; default: throw new Exception('Invalid action', 400); } break; case 'PUT': if (!$userId) { throw new Exception('Authentication required', 401); } handleUpdateProfile($userId); break; case 'DELETE': if (!$userId) { throw new Exception('Authentication required', 401); } handleDeleteAccount($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; } /** * Get current user's profile */ function handleGetProfile($userId) { global $class_database, $response; if (!$userId) { throw new Exception('Authentication required', 401); } $sql = "SELECT usr_id, usr_user, usr_dname, usr_email, usr_fname, usr_lname, usr_avatar, usr_about, usr_website, usr_location, usr_verified, usr_partner, usr_affiliate, usr_joined, usr_lastlogin, usr_profile_privacy FROM db_users WHERE usr_id = ?"; $result = $class_database->execute($sql, [$userId]); if (!$result || $result->RecordCount() === 0) { throw new Exception('User not found', 404); } $user = $result->fields; // Get user stats $statsSql = "SELECT (SELECT COUNT(*) FROM db_videofiles WHERE usr_id = ? AND approved = 1) as video_count, (SELECT COUNT(*) FROM db_subscriptions WHERE channel_id = ?) as subscriber_count, (SELECT COUNT(*) FROM db_subscriptions WHERE usr_id = ?) as subscription_count, (SELECT SUM(file_views) FROM db_videofiles WHERE usr_id = ?) as total_views FROM dual"; $stats = $class_database->execute($statsSql, [$userId, $userId, $userId, $userId]); if ($stats && $stats->RecordCount() > 0) { $user['stats'] = [ 'videos' => (int)$stats->fields['video_count'], 'subscribers' => (int)$stats->fields['subscriber_count'], 'subscriptions' => (int)$stats->fields['subscription_count'], 'views' => (int)$stats->fields['total_views'] ]; } // Get preferences if they exist $prefSql = "SELECT * FROM db_user_preferences WHERE usr_id = ?"; $prefs = $class_database->execute($prefSql, [$userId]); if ($prefs && $prefs->RecordCount() > 0) { $user['preferences'] = $prefs->fields; } $response['success'] = true; $response['data'] = $user; echo json_encode($response); } /** * Get another user's public profile */ function handleGetPublicProfile($targetUserId, $currentUserId) { global $class_database, $response; $sql = "SELECT usr_id, usr_user, usr_dname, usr_avatar, usr_about, usr_website, usr_location, usr_verified, usr_partner, usr_joined, usr_profile_privacy FROM db_users WHERE usr_id = ?"; $result = $class_database->execute($sql, [$targetUserId]); if (!$result || $result->RecordCount() === 0) { throw new Exception('User not found', 404); } $user = $result->fields; // Check privacy settings if ($user['usr_profile_privacy'] === 'private' && $targetUserId != $currentUserId) { throw new Exception('This profile is private', 403); } // Get public stats $statsSql = "SELECT (SELECT COUNT(*) FROM db_videofiles WHERE usr_id = ? AND approved = 1 AND privacy = 'public') as video_count, (SELECT COUNT(*) FROM db_subscriptions WHERE channel_id = ?) as subscriber_count, (SELECT SUM(file_views) FROM db_videofiles WHERE usr_id = ? AND approved = 1) as total_views FROM dual"; $stats = $class_database->execute($statsSql, [$targetUserId, $targetUserId, $targetUserId]); if ($stats && $stats->RecordCount() > 0) { $user['stats'] = [ 'videos' => (int)$stats->fields['video_count'], 'subscribers' => (int)$stats->fields['subscriber_count'], 'views' => (int)$stats->fields['total_views'] ]; } // Check if current user is subscribed if ($currentUserId) { $subSql = "SELECT 1 FROM db_subscriptions WHERE usr_id = ? AND channel_id = ?"; $subResult = $class_database->execute($subSql, [$currentUserId, $targetUserId]); $user['is_subscribed'] = $subResult && $subResult->RecordCount() > 0; } $response['success'] = true; $response['data'] = $user; echo json_encode($response); } /** * Update user profile */ function handleUpdateProfile($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); } // Build update query dynamically $updates = []; $params = []; $allowedFields = [ 'usr_dname', 'usr_fname', 'usr_lname', 'usr_about', 'usr_website', 'usr_location', 'usr_profile_privacy' ]; 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[] = $userId; $sql = "UPDATE db_users SET " . implode(', ', $updates) . " WHERE usr_id = ?"; $result = $class_database->execute($sql, $params); if (!$result) { throw new Exception('Failed to update profile', 500); } // Log the update VLogger::log('info', 'Profile updated', ['user_id' => $userId, 'fields' => array_keys($input)]); $response['success'] = true; $response['data'] = ['message' => 'Profile updated successfully']; echo json_encode($response); } /** * Upload avatar */ function handleUploadAvatar($userId) { global $class_database, $response; if (!isset($_FILES['avatar']) || $_FILES['avatar']['error'] !== UPLOAD_ERR_OK) { throw new Exception('No file uploaded or upload error occurred', 400); } $file = $_FILES['avatar']; // Validate file type $allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']; $finfo = finfo_open(FILEINFO_MIME_TYPE); $mimeType = finfo_file($finfo, $file['tmp_name']); finfo_close($finfo); if (!in_array($mimeType, $allowedTypes)) { throw new Exception('Invalid file type. Only JPG, PNG, and GIF are allowed', 400); } // Validate file size (max 5MB) if ($file['size'] > 5 * 1024 * 1024) { throw new Exception('File too large. Maximum size is 5MB', 400); } // Create upload directory if it doesn't exist $uploadDir = dirname(__FILE__) . '/../f_data/data_userfiles/user_profile/' . $userId . '/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } // Generate unique filename $extension = pathinfo($file['name'], PATHINFO_EXTENSION); $filename = 'avatar_' . time() . '.' . $extension; $targetPath = $uploadDir . $filename; // Move uploaded file if (!move_uploaded_file($file['tmp_name'], $targetPath)) { throw new Exception('Failed to save uploaded file', 500); } // Update user avatar in database $avatarUrl = '/f_data/data_userfiles/user_profile/' . $userId . '/' . $filename; $sql = "UPDATE db_users SET usr_avatar = ? WHERE usr_id = ?"; $result = $class_database->execute($sql, [$avatarUrl, $userId]); if (!$result) { throw new Exception('Failed to update avatar in database', 500); } $response['success'] = true; $response['data'] = [ 'message' => 'Avatar uploaded successfully', 'avatar_url' => $avatarUrl ]; echo json_encode($response); } /** * Get user statistics */ function handleGetStats($userId) { global $class_database, $response; if (!$userId) { throw new Exception('Authentication required', 401); } $sql = "SELECT (SELECT COUNT(*) FROM db_videofiles WHERE usr_id = ? AND approved = 1) as total_videos, (SELECT COUNT(*) FROM db_videofiles WHERE usr_id = ? AND upload_date >= DATE_SUB(NOW(), INTERVAL 30 DAY)) as videos_last_30_days, (SELECT SUM(file_views) FROM db_videofiles WHERE usr_id = ?) as total_views, (SELECT COUNT(*) FROM db_subscriptions WHERE channel_id = ?) as total_subscribers, (SELECT COUNT(*) FROM db_subscriptions WHERE usr_id = ?) as total_subscriptions, (SELECT COUNT(*) FROM db_comments WHERE usr_id = ?) as total_comments, (SELECT COUNT(*) FROM db_likes WHERE usr_id = ?) as total_likes_given FROM dual"; $result = $class_database->execute($sql, [$userId, $userId, $userId, $userId, $userId, $userId, $userId]); if (!$result || $result->RecordCount() === 0) { throw new Exception('Failed to fetch statistics', 500); } $response['success'] = true; $response['data'] = $result->fields; echo json_encode($response); } /** * Get user's videos */ function handleGetUserVideos($userId) { global $class_database, $response; $targetUserId = isset($_GET['id']) ? (int)$_GET['id'] : $userId; if (!$targetUserId) { throw new Exception('User ID required', 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; // Privacy filter $privacyWhere = ($targetUserId == $userId) ? "" : "AND privacy = 'public'"; // Get total count $countSql = "SELECT COUNT(*) FROM db_videofiles WHERE usr_id = ? AND approved = 1 $privacyWhere"; $total = (int)$class_database->singleFieldValue($countSql, [$targetUserId]); // Get videos $sql = "SELECT file_key, file_title, file_description, file_duration, file_views, privacy, upload_date, thumbnail, featured, (SELECT COUNT(*) FROM db_likes WHERE file_key = db_videofiles.file_key AND like_type = 'like') as like_count, (SELECT COUNT(*) FROM db_comments WHERE file_key = db_videofiles.file_key) as comment_count FROM db_videofiles WHERE usr_id = ? AND approved = 1 $privacyWhere ORDER BY upload_date DESC LIMIT ? OFFSET ?"; $videos = $class_database->execute($sql, [$targetUserId, $limit, $offset]); $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 user's subscriptions */ function handleGetSubscriptions($userId) { global $class_database, $response; if (!$userId) { throw new Exception('Authentication required', 401); } $sql = "SELECT u.usr_id, u.usr_user, u.usr_dname, u.usr_avatar, s.sub_date, (SELECT COUNT(*) FROM db_videofiles WHERE usr_id = u.usr_id AND approved = 1) as video_count FROM db_subscriptions s LEFT JOIN db_users u ON s.channel_id = u.usr_id WHERE s.usr_id = ? ORDER BY s.sub_date DESC"; $result = $class_database->execute($sql, [$userId]); $response['success'] = true; $response['data'] = $result ? $result->GetArray() : []; echo json_encode($response); } /** * Get user's subscribers */ function handleGetSubscribers($userId) { global $class_database, $response; $targetUserId = isset($_GET['id']) ? (int)$_GET['id'] : $userId; if (!$targetUserId) { throw new Exception('User ID required', 400); } $sql = "SELECT u.usr_id, u.usr_user, u.usr_dname, u.usr_avatar, s.sub_date FROM db_subscriptions s LEFT JOIN db_users u ON s.usr_id = u.usr_id WHERE s.channel_id = ? ORDER BY s.sub_date DESC"; $result = $class_database->execute($sql, [$targetUserId]); $response['success'] = true; $response['data'] = $result ? $result->GetArray() : []; echo json_encode($response); } /** * Subscribe to a channel */ function handleSubscribe($userId) { global $class_database, $response; $input = json_decode(file_get_contents('php://input'), true); $channelId = isset($input['channel_id']) ? (int)$input['channel_id'] : null; if (!$channelId) { throw new Exception('Channel ID is required', 400); } if ($channelId == $userId) { throw new Exception('Cannot subscribe to yourself', 400); } // Check if already subscribed $checkSql = "SELECT 1 FROM db_subscriptions WHERE usr_id = ? AND channel_id = ?"; $existing = $class_database->execute($checkSql, [$userId, $channelId]); if ($existing && $existing->RecordCount() > 0) { throw new Exception('Already subscribed', 400); } // Add subscription $sql = "INSERT INTO db_subscriptions (usr_id, channel_id, sub_date) VALUES (?, ?, NOW())"; $result = $class_database->execute($sql, [$userId, $channelId]); if (!$result) { throw new Exception('Failed to subscribe', 500); } // Create notification for channel owner $notifSql = "INSERT INTO db_notifications (usr_id, notif_type, notif_from, notif_date) VALUES (?, 'subscription', ?, NOW())"; $class_database->execute($notifSql, [$channelId, $userId]); $response['success'] = true; $response['data'] = ['message' => 'Subscribed successfully']; echo json_encode($response); } /** * Unsubscribe from a channel */ function handleUnsubscribe($userId) { global $class_database, $response; $input = json_decode(file_get_contents('php://input'), true); $channelId = isset($input['channel_id']) ? (int)$input['channel_id'] : null; if (!$channelId) { throw new Exception('Channel ID is required', 400); } // Remove subscription $sql = "DELETE FROM db_subscriptions WHERE usr_id = ? AND channel_id = ?"; $result = $class_database->execute($sql, [$userId, $channelId]); if (!$result) { throw new Exception('Failed to unsubscribe', 500); } $response['success'] = true; $response['data'] = ['message' => 'Unsubscribed successfully']; echo json_encode($response); } /** * Delete user account */ function handleDeleteAccount($userId) { global $class_database, $response; // This is a destructive operation, so we'll just mark the account as deleted // rather than actually deleting it $sql = "UPDATE db_users SET usr_status = 'deleted', usr_email = CONCAT('deleted_', usr_id, '@deleted.com') WHERE usr_id = ?"; $result = $class_database->execute($sql, [$userId]); if (!$result) { throw new Exception('Failed to delete account', 500); } // Log out the user VAuth::logout(); $response['success'] = true; $response['data'] = ['message' => 'Account deleted successfully']; echo json_encode($response); }