Sync current dev state
This commit is contained in:
582
api/videos.php
Normal file
582
api/videos.php
Normal file
@@ -0,0 +1,582 @@
|
||||
<?php
|
||||
/**
|
||||
* Videos API Endpoint
|
||||
* Handles all video-related operations
|
||||
*
|
||||
* Supported Actions:
|
||||
* - GET list: List videos with pagination and filters
|
||||
* - GET single: Get single video details
|
||||
* - POST create: Create/upload new video
|
||||
* - PUT update: Update video details
|
||||
* - DELETE delete: Delete video
|
||||
* - POST like: Like/unlike video
|
||||
* - POST view: Increment view count
|
||||
*/
|
||||
|
||||
// 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'] : 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);
|
||||
}
|
||||
Reference in New Issue
Block a user