Sync current dev state
This commit is contained in:
581
api/user.php
Normal file
581
api/user.php
Normal file
@@ -0,0 +1,581 @@
|
||||
<?php
|
||||
/**
|
||||
* User API Endpoint
|
||||
* Handles user profile and account operations
|
||||
*
|
||||
* Supported Actions:
|
||||
* - GET profile: Get user profile
|
||||
* - PUT update: Update user profile
|
||||
* - POST avatar: Upload avatar
|
||||
* - GET stats: Get user statistics
|
||||
* - GET videos: Get user's videos
|
||||
*/
|
||||
|
||||
// Include CORS configuration
|
||||
require_once __DIR__ . '/cors.config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Include core configuration
|
||||
require_once dirname(__FILE__) . '/../f_core/config.core.php';
|
||||
|
||||
// Initialize response
|
||||
$response = ['success' => 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);
|
||||
}
|
||||
Reference in New Issue
Block a user