Sync current dev state
This commit is contained in:
552
api/comments.php
Normal file
552
api/comments.php
Normal file
@@ -0,0 +1,552 @@
|
||||
<?php
|
||||
/**
|
||||
* Comments API Endpoint
|
||||
* Handles all comment-related operations
|
||||
*
|
||||
* Supported Actions:
|
||||
* - GET list: List comments for a video
|
||||
* - POST create: Create new comment
|
||||
* - PUT update: Update comment
|
||||
* - DELETE delete: Delete comment
|
||||
* - POST like: Like/unlike comment
|
||||
* - POST report: Report comment
|
||||
*/
|
||||
|
||||
// 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 comment
|
||||
handleGetComment($_GET['id'], $userId);
|
||||
} else {
|
||||
// List comments
|
||||
handleListComments($userId);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
if (!$userId) {
|
||||
throw new Exception('Authentication required', 401);
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'create':
|
||||
case null:
|
||||
handleCreateComment($userId);
|
||||
break;
|
||||
case 'like':
|
||||
handleLikeComment($userId);
|
||||
break;
|
||||
case 'report':
|
||||
handleReportComment($userId);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invalid action', 400);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
if (!$userId) {
|
||||
throw new Exception('Authentication required', 401);
|
||||
}
|
||||
handleUpdateComment($userId);
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
if (!$userId) {
|
||||
throw new Exception('Authentication required', 401);
|
||||
}
|
||||
handleDeleteComment($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 comments for a video
|
||||
*/
|
||||
function handleListComments($userId) {
|
||||
global $class_database, $response;
|
||||
|
||||
$fileKey = isset($_GET['file_key']) ? $_GET['file_key'] : null;
|
||||
|
||||
if (!$fileKey) {
|
||||
throw new Exception('file_key parameter is required', 400);
|
||||
}
|
||||
|
||||
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
|
||||
$limit = isset($_GET['limit']) ? min(100, max(1, (int)$_GET['limit'])) : 50;
|
||||
$offset = ($page - 1) * $limit;
|
||||
$sort = isset($_GET['sort']) ? $_GET['sort'] : 'recent'; // recent, top, oldest
|
||||
|
||||
// Determine sort order
|
||||
$orderBy = match($sort) {
|
||||
'top' => 'c.comment_likes DESC, c.comment_date DESC',
|
||||
'oldest' => 'c.comment_date ASC',
|
||||
default => 'c.comment_date DESC'
|
||||
};
|
||||
|
||||
// Get total count
|
||||
$countSql = "SELECT COUNT(*) FROM db_comments WHERE file_key = ? AND parent_id IS NULL";
|
||||
$total = (int)$class_database->singleFieldValue($countSql, [$fileKey]);
|
||||
|
||||
// Get top-level comments (not replies)
|
||||
$sql = "SELECT c.comment_id, c.usr_id, c.comment_text, c.comment_date,
|
||||
c.comment_likes, c.parent_id,
|
||||
u.usr_user, u.usr_dname, u.usr_avatar, u.usr_verified,
|
||||
(SELECT COUNT(*) FROM db_comments WHERE parent_id = c.comment_id) as reply_count
|
||||
FROM db_comments c
|
||||
LEFT JOIN db_users u ON c.usr_id = u.usr_id
|
||||
WHERE c.file_key = ? AND c.parent_id IS NULL
|
||||
ORDER BY $orderBy
|
||||
LIMIT ? OFFSET ?";
|
||||
|
||||
$comments = $class_database->execute($sql, [$fileKey, $limit, $offset]);
|
||||
|
||||
$commentList = $comments ? $comments->GetArray() : [];
|
||||
|
||||
// If user is logged in, check which comments they've liked
|
||||
if ($userId && !empty($commentList)) {
|
||||
$commentIds = array_column($commentList, 'comment_id');
|
||||
$placeholders = implode(',', array_fill(0, count($commentIds), '?'));
|
||||
|
||||
$likeSql = "SELECT comment_id FROM db_comment_likes WHERE comment_id IN ($placeholders) AND usr_id = ?";
|
||||
$likedComments = $class_database->execute($likeSql, array_merge($commentIds, [$userId]));
|
||||
|
||||
$likedIds = [];
|
||||
if ($likedComments) {
|
||||
while (!$likedComments->EOF) {
|
||||
$likedIds[] = $likedComments->fields['comment_id'];
|
||||
$likedComments->MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
// Add liked status to comments
|
||||
foreach ($commentList as &$comment) {
|
||||
$comment['user_liked'] = in_array($comment['comment_id'], $likedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
$response['data'] = [
|
||||
'comments' => $commentList,
|
||||
'pagination' => [
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'total' => $total,
|
||||
'pages' => ceil($total / $limit)
|
||||
]
|
||||
];
|
||||
|
||||
echo json_encode($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single comment with replies
|
||||
*/
|
||||
function handleGetComment($commentId, $userId) {
|
||||
global $class_database, $response;
|
||||
|
||||
// Get the comment
|
||||
$sql = "SELECT c.comment_id, c.usr_id, c.file_key, c.comment_text, c.comment_date,
|
||||
c.comment_likes, c.parent_id,
|
||||
u.usr_user, u.usr_dname, u.usr_avatar, u.usr_verified
|
||||
FROM db_comments c
|
||||
LEFT JOIN db_users u ON c.usr_id = u.usr_id
|
||||
WHERE c.comment_id = ?";
|
||||
|
||||
$result = $class_database->execute($sql, [$commentId]);
|
||||
|
||||
if (!$result || $result->RecordCount() === 0) {
|
||||
throw new Exception('Comment not found', 404);
|
||||
}
|
||||
|
||||
$comment = $result->fields;
|
||||
|
||||
// Get replies
|
||||
$repliesSql = "SELECT c.comment_id, c.usr_id, c.comment_text, c.comment_date,
|
||||
c.comment_likes, c.parent_id,
|
||||
u.usr_user, u.usr_dname, u.usr_avatar, u.usr_verified
|
||||
FROM db_comments c
|
||||
LEFT JOIN db_users u ON c.usr_id = u.usr_id
|
||||
WHERE c.parent_id = ?
|
||||
ORDER BY c.comment_date ASC";
|
||||
|
||||
$replies = $class_database->execute($repliesSql, [$commentId]);
|
||||
$comment['replies'] = $replies ? $replies->GetArray() : [];
|
||||
|
||||
// Check if user liked this comment
|
||||
if ($userId) {
|
||||
$likeSql = "SELECT 1 FROM db_comment_likes WHERE comment_id = ? AND usr_id = ?";
|
||||
$liked = $class_database->execute($likeSql, [$commentId, $userId]);
|
||||
$comment['user_liked'] = $liked && $liked->RecordCount() > 0;
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
$response['data'] = $comment;
|
||||
|
||||
echo json_encode($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new comment
|
||||
*/
|
||||
function handleCreateComment($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);
|
||||
}
|
||||
|
||||
$fileKey = isset($input['file_key']) ? $input['file_key'] : null;
|
||||
$commentText = isset($input['comment_text']) ? trim($input['comment_text']) : '';
|
||||
$parentId = isset($input['parent_id']) ? (int)$input['parent_id'] : null;
|
||||
|
||||
// Validate required fields
|
||||
if (!$fileKey) {
|
||||
throw new Exception('file_key is required', 400);
|
||||
}
|
||||
|
||||
if (empty($commentText)) {
|
||||
throw new Exception('Comment text cannot be empty', 400);
|
||||
}
|
||||
|
||||
if (strlen($commentText) > 5000) {
|
||||
throw new Exception('Comment text too long (max 5000 characters)', 400);
|
||||
}
|
||||
|
||||
// Sanitize comment text
|
||||
$commentText = VSecurity::sanitize($commentText);
|
||||
|
||||
// Check if video exists
|
||||
$videoCheckSql = "SELECT 1 FROM db_videofiles WHERE file_key = ?";
|
||||
$videoExists = $class_database->execute($videoCheckSql, [$fileKey]);
|
||||
|
||||
if (!$videoExists || $videoExists->RecordCount() === 0) {
|
||||
throw new Exception('Video not found', 404);
|
||||
}
|
||||
|
||||
// If this is a reply, check if parent comment exists
|
||||
if ($parentId) {
|
||||
$parentCheckSql = "SELECT usr_id FROM db_comments WHERE comment_id = ?";
|
||||
$parentExists = $class_database->execute($parentCheckSql, [$parentId]);
|
||||
|
||||
if (!$parentExists || $parentExists->RecordCount() === 0) {
|
||||
throw new Exception('Parent comment not found', 404);
|
||||
}
|
||||
|
||||
$parentUserId = $parentExists->fields['usr_id'];
|
||||
}
|
||||
|
||||
// Insert comment
|
||||
$sql = "INSERT INTO db_comments (file_key, usr_id, comment_text, comment_date, parent_id, comment_likes)
|
||||
VALUES (?, ?, ?, NOW(), ?, 0)";
|
||||
|
||||
$result = $class_database->execute($sql, [$fileKey, $userId, $commentText, $parentId]);
|
||||
|
||||
if (!$result) {
|
||||
throw new Exception('Failed to create comment', 500);
|
||||
}
|
||||
|
||||
// Get the inserted comment ID
|
||||
$commentId = $class_database->Insert_ID();
|
||||
|
||||
// If this is a reply, create a notification for the parent comment author
|
||||
if ($parentId && $parentUserId != $userId) {
|
||||
$notifSql = "INSERT INTO db_notifications (usr_id, notif_type, notif_from, notif_ref_id, notif_date)
|
||||
VALUES (?, 'comment_reply', ?, ?, NOW())";
|
||||
$class_database->execute($notifSql, [$parentUserId, $userId, $commentId]);
|
||||
}
|
||||
|
||||
// Get video owner and create notification
|
||||
$videoOwnerSql = "SELECT usr_id FROM db_videofiles WHERE file_key = ?";
|
||||
$videoOwner = $class_database->execute($videoOwnerSql, [$fileKey]);
|
||||
|
||||
if ($videoOwner && $videoOwner->fields['usr_id'] != $userId) {
|
||||
$notifSql = "INSERT INTO db_notifications (usr_id, notif_type, notif_from, notif_ref_id, notif_date)
|
||||
VALUES (?, 'comment', ?, ?, NOW())";
|
||||
$class_database->execute($notifSql, [$videoOwner->fields['usr_id'], $userId, $commentId]);
|
||||
}
|
||||
|
||||
// Return the created comment
|
||||
$getCommentSql = "SELECT c.comment_id, c.usr_id, c.file_key, c.comment_text, c.comment_date,
|
||||
c.comment_likes, c.parent_id,
|
||||
u.usr_user, u.usr_dname, u.usr_avatar, u.usr_verified
|
||||
FROM db_comments c
|
||||
LEFT JOIN db_users u ON c.usr_id = u.usr_id
|
||||
WHERE c.comment_id = ?";
|
||||
|
||||
$newComment = $class_database->execute($getCommentSql, [$commentId]);
|
||||
|
||||
$response['success'] = true;
|
||||
$response['data'] = $newComment->fields;
|
||||
|
||||
echo json_encode($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update comment
|
||||
*/
|
||||
function handleUpdateComment($userId) {
|
||||
global $class_database, $response;
|
||||
|
||||
$commentId = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
if (!$commentId) {
|
||||
throw new Exception('Comment ID is required', 400);
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
$checkSql = "SELECT usr_id FROM db_comments WHERE comment_id = ?";
|
||||
$checkResult = $class_database->execute($checkSql, [$commentId]);
|
||||
|
||||
if (!$checkResult || $checkResult->RecordCount() === 0) {
|
||||
throw new Exception('Comment not found', 404);
|
||||
}
|
||||
|
||||
if ($checkResult->fields['usr_id'] != $userId) {
|
||||
throw new Exception('You do not have permission to edit this comment', 403);
|
||||
}
|
||||
|
||||
// Get JSON input
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input || !isset($input['comment_text'])) {
|
||||
throw new Exception('comment_text is required', 400);
|
||||
}
|
||||
|
||||
$commentText = trim($input['comment_text']);
|
||||
|
||||
if (empty($commentText)) {
|
||||
throw new Exception('Comment text cannot be empty', 400);
|
||||
}
|
||||
|
||||
if (strlen($commentText) > 5000) {
|
||||
throw new Exception('Comment text too long (max 5000 characters)', 400);
|
||||
}
|
||||
|
||||
$commentText = VSecurity::sanitize($commentText);
|
||||
|
||||
// Update comment
|
||||
$sql = "UPDATE db_comments SET comment_text = ?, comment_edited = 1 WHERE comment_id = ?";
|
||||
$result = $class_database->execute($sql, [$commentText, $commentId]);
|
||||
|
||||
if (!$result) {
|
||||
throw new Exception('Failed to update comment', 500);
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
$response['data'] = ['message' => 'Comment updated successfully'];
|
||||
|
||||
echo json_encode($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete comment
|
||||
*/
|
||||
function handleDeleteComment($userId) {
|
||||
global $class_database, $response;
|
||||
|
||||
$commentId = isset($_GET['id']) ? (int)$_GET['id'] : null;
|
||||
|
||||
if (!$commentId) {
|
||||
throw new Exception('Comment ID is required', 400);
|
||||
}
|
||||
|
||||
// Verify ownership or admin status
|
||||
$checkSql = "SELECT usr_id FROM db_comments WHERE comment_id = ?";
|
||||
$checkResult = $class_database->execute($checkSql, [$commentId]);
|
||||
|
||||
if (!$checkResult || $checkResult->RecordCount() === 0) {
|
||||
throw new Exception('Comment not found', 404);
|
||||
}
|
||||
|
||||
// Check if user is the comment owner or has admin role
|
||||
$isOwner = $checkResult->fields['usr_id'] == $userId;
|
||||
$isAdmin = VAuth::hasPermission('comments.delete.any');
|
||||
|
||||
if (!$isOwner && !$isAdmin) {
|
||||
throw new Exception('You do not have permission to delete this comment', 403);
|
||||
}
|
||||
|
||||
// Delete comment and its replies
|
||||
$sql = "DELETE FROM db_comments WHERE comment_id = ? OR parent_id = ?";
|
||||
$result = $class_database->execute($sql, [$commentId, $commentId]);
|
||||
|
||||
if (!$result) {
|
||||
throw new Exception('Failed to delete comment', 500);
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
$response['data'] = ['message' => 'Comment deleted successfully'];
|
||||
|
||||
echo json_encode($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like/unlike comment
|
||||
*/
|
||||
function handleLikeComment($userId) {
|
||||
global $class_database, $response;
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$commentId = isset($input['comment_id']) ? (int)$input['comment_id'] : null;
|
||||
|
||||
if (!$commentId) {
|
||||
throw new Exception('comment_id is required', 400);
|
||||
}
|
||||
|
||||
// Check if comment exists
|
||||
$checkSql = "SELECT usr_id FROM db_comments WHERE comment_id = ?";
|
||||
$commentExists = $class_database->execute($checkSql, [$commentId]);
|
||||
|
||||
if (!$commentExists || $commentExists->RecordCount() === 0) {
|
||||
throw new Exception('Comment not found', 404);
|
||||
}
|
||||
|
||||
$commentOwnerId = $commentExists->fields['usr_id'];
|
||||
|
||||
// Check if already liked
|
||||
$likeSql = "SELECT 1 FROM db_comment_likes WHERE comment_id = ? AND usr_id = ?";
|
||||
$existing = $class_database->execute($likeSql, [$commentId, $userId]);
|
||||
|
||||
if ($existing && $existing->RecordCount() > 0) {
|
||||
// Remove like
|
||||
$deleteSql = "DELETE FROM db_comment_likes WHERE comment_id = ? AND usr_id = ?";
|
||||
$class_database->execute($deleteSql, [$commentId, $userId]);
|
||||
|
||||
// Decrement like count
|
||||
$updateSql = "UPDATE db_comments SET comment_likes = comment_likes - 1 WHERE comment_id = ?";
|
||||
$class_database->execute($updateSql, [$commentId]);
|
||||
|
||||
$action = 'unliked';
|
||||
} else {
|
||||
// Add like
|
||||
$insertSql = "INSERT INTO db_comment_likes (comment_id, usr_id, like_date) VALUES (?, ?, NOW())";
|
||||
$class_database->execute($insertSql, [$commentId, $userId]);
|
||||
|
||||
// Increment like count
|
||||
$updateSql = "UPDATE db_comments SET comment_likes = comment_likes + 1 WHERE comment_id = ?";
|
||||
$class_database->execute($updateSql, [$commentId]);
|
||||
|
||||
// Create notification for comment owner
|
||||
if ($commentOwnerId != $userId) {
|
||||
$notifSql = "INSERT INTO db_notifications (usr_id, notif_type, notif_from, notif_ref_id, notif_date)
|
||||
VALUES (?, 'comment_like', ?, ?, NOW())";
|
||||
$class_database->execute($notifSql, [$commentOwnerId, $userId, $commentId]);
|
||||
}
|
||||
|
||||
$action = 'liked';
|
||||
}
|
||||
|
||||
// Get updated like count
|
||||
$countSql = "SELECT comment_likes FROM db_comments WHERE comment_id = ?";
|
||||
$countResult = $class_database->execute($countSql, [$commentId]);
|
||||
|
||||
$response['success'] = true;
|
||||
$response['data'] = [
|
||||
'action' => $action,
|
||||
'like_count' => $countResult->fields['comment_likes']
|
||||
];
|
||||
|
||||
echo json_encode($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report comment
|
||||
*/
|
||||
function handleReportComment($userId) {
|
||||
global $class_database, $response;
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$commentId = isset($input['comment_id']) ? (int)$input['comment_id'] : null;
|
||||
$reason = isset($input['reason']) ? VSecurity::sanitize($input['reason']) : '';
|
||||
|
||||
if (!$commentId) {
|
||||
throw new Exception('comment_id is required', 400);
|
||||
}
|
||||
|
||||
if (empty($reason)) {
|
||||
throw new Exception('Reason is required', 400);
|
||||
}
|
||||
|
||||
// Check if comment exists
|
||||
$checkSql = "SELECT 1 FROM db_comments WHERE comment_id = ?";
|
||||
$commentExists = $class_database->execute($checkSql, [$commentId]);
|
||||
|
||||
if (!$commentExists || $commentExists->RecordCount() === 0) {
|
||||
throw new Exception('Comment not found', 404);
|
||||
}
|
||||
|
||||
// Check if already reported by this user
|
||||
$reportCheckSql = "SELECT 1 FROM db_reports WHERE comment_id = ? AND usr_id = ?";
|
||||
$existing = $class_database->execute($reportCheckSql, [$commentId, $userId]);
|
||||
|
||||
if ($existing && $existing->RecordCount() > 0) {
|
||||
throw new Exception('You have already reported this comment', 400);
|
||||
}
|
||||
|
||||
// Insert report
|
||||
$sql = "INSERT INTO db_reports (comment_id, usr_id, report_reason, report_date, report_status)
|
||||
VALUES (?, ?, ?, NOW(), 'pending')";
|
||||
|
||||
$result = $class_database->execute($sql, [$commentId, $userId, $reason]);
|
||||
|
||||
if (!$result) {
|
||||
throw new Exception('Failed to submit report', 500);
|
||||
}
|
||||
|
||||
$response['success'] = true;
|
||||
$response['data'] = ['message' => 'Comment reported successfully'];
|
||||
|
||||
echo json_encode($response);
|
||||
}
|
||||
Reference in New Issue
Block a user