feat: Add comprehensive documentation suite and reorganize project structure

- Created complete documentation in docs/ directory
- Added PROJECT_OVERVIEW.md with feature highlights and getting started guide
- Added ARCHITECTURE.md with system design and technical details
- Added SECURITY.md with comprehensive security implementation guide
- Added DEVELOPMENT.md with development workflows and best practices
- Added DEPLOYMENT.md with production deployment instructions
- Added API.md with complete REST API documentation
- Added CONTRIBUTING.md with contribution guidelines
- Added CHANGELOG.md with version history and migration notes
- Reorganized all documentation files into docs/ directory for better organization
- Updated README.md with proper documentation links and quick navigation
- Enhanced project structure with professional documentation standards
This commit is contained in:
SamiAhmed7777
2025-10-21 00:39:45 -07:00
commit 0b7e2d0a5b
6080 changed files with 1332936 additions and 0 deletions

View File

@@ -0,0 +1,803 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
defined('_ISVALID') or header('Location: /error');
/**
* Admin Dashboard and Management System
*/
class VAdmin
{
private $logger;
private $db;
private static $instance = null;
public function __construct()
{
$this->logger = VLogger::getInstance();
$this->db = VDatabase::getInstance();
}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Get platform analytics dashboard data
* @param string $period Time period (today, week, month, year)
* @return array Dashboard data
*/
public function getDashboardAnalytics($period = 'week')
{
try {
$timeFilter = $this->getTimeFilter($period);
$analytics = [
'overview' => $this->getOverviewStats($timeFilter),
'users' => $this->getUserStats($timeFilter),
'content' => $this->getContentStats($timeFilter),
'engagement' => $this->getEngagementStats($timeFilter),
'system' => $this->getSystemStats(),
'recent_activity' => $this->getRecentActivity(20)
];
return [
'success' => true,
'period' => $period,
'analytics' => $analytics,
'generated_at' => date('Y-m-d H:i:s')
];
} catch (Exception $e) {
$this->logger->error('Failed to get dashboard analytics', [
'period' => $period,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Get user management data
* @param array $filters Filters
* @param int $limit Limit
* @param int $offset Offset
* @return array User data
*/
public function getUserManagement($filters = [], $limit = 50, $offset = 0)
{
try {
$whereClause = "WHERE 1=1";
$params = [];
if (isset($filters['role'])) {
$whereClause .= " AND usr_role = ?";
$params[] = $filters['role'];
}
if (isset($filters['status'])) {
if ($filters['status'] === 'active') {
$whereClause .= " AND usr_active = 1 AND usr_deleted = 0";
} elseif ($filters['status'] === 'suspended') {
$whereClause .= " AND usr_active = 0 AND usr_deleted = 0";
} elseif ($filters['status'] === 'deleted') {
$whereClause .= " AND usr_deleted = 1";
}
}
if (isset($filters['search'])) {
$whereClause .= " AND (usr_user LIKE ? OR usr_email LIKE ?)";
$searchTerm = '%' . $filters['search'] . '%';
$params[] = $searchTerm;
$params[] = $searchTerm;
}
$query = "SELECT usr_id, usr_user, usr_email, usr_role, usr_active, usr_deleted,
usr_verified, usr_joindate, usr_lastlogin, usr_logins,
usr_v_count, usr_i_count, usr_a_count, usr_d_count
FROM db_accountuser
{$whereClause}
ORDER BY usr_joindate DESC
LIMIT ? OFFSET ?";
$params[] = $limit;
$params[] = $offset;
$result = $this->db->doQuery($query, $params);
$users = [];
while ($row = $this->db->doFetch($result)) {
$users[] = $this->formatUserData($row);
}
// Get total count
$countQuery = "SELECT COUNT(*) as total FROM db_accountuser {$whereClause}";
$countParams = array_slice($params, 0, -2); // Remove limit and offset
$countResult = $this->db->doQuery($countQuery, $countParams);
$totalCount = $this->db->doFetch($countResult)['total'];
return [
'success' => true,
'users' => $users,
'pagination' => [
'total' => $totalCount,
'limit' => $limit,
'offset' => $offset,
'pages' => ceil($totalCount / $limit)
]
];
} catch (Exception $e) {
$this->logger->error('Failed to get user management data', [
'filters' => $filters,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Update user status
* @param int $userId User ID
* @param string $action Action (activate, suspend, delete, restore)
* @param string $reason Reason for action
* @return array Result
*/
public function updateUserStatus($userId, $action, $reason = '')
{
try {
$user = $this->getUserById($userId);
if (!$user) {
return [
'success' => false,
'error' => 'User not found'
];
}
$updateData = ['updated_at' => date('Y-m-d H:i:s')];
switch ($action) {
case 'activate':
$updateData['usr_active'] = 1;
$updateData['usr_deleted'] = 0;
break;
case 'suspend':
$updateData['usr_active'] = 0;
break;
case 'delete':
$updateData['usr_deleted'] = 1;
$updateData['usr_active'] = 0;
break;
case 'restore':
$updateData['usr_deleted'] = 0;
$updateData['usr_active'] = 1;
break;
default:
return [
'success' => false,
'error' => 'Invalid action'
];
}
$this->db->doUpdate('db_accountuser', 'usr_id', $updateData, $userId);
// Log admin action
$this->logAdminAction('user_status_update', [
'target_user_id' => $userId,
'target_username' => $user['usr_user'],
'action' => $action,
'reason' => $reason
]);
$this->logger->info('User status updated', [
'user_id' => $userId,
'action' => $action,
'reason' => $reason
]);
return [
'success' => true,
'message' => "User {$action}d successfully"
];
} catch (Exception $e) {
$this->logger->error('Failed to update user status', [
'user_id' => $userId,
'action' => $action,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Get content moderation queue
* @param string $contentType Content type filter
* @param int $limit Limit
* @param int $offset Offset
* @return array Moderation queue
*/
public function getModerationQueue($contentType = 'all', $limit = 50, $offset = 0)
{
try {
$tables = [
'video' => 'db_videofiles',
'image' => 'db_imagefiles',
'audio' => 'db_audiofiles',
'document' => 'db_documentfiles'
];
$items = [];
if ($contentType === 'all') {
foreach ($tables as $type => $table) {
$typeItems = $this->getModerationItemsByType($type, $table, $limit / 4, $offset);
$items = array_merge($items, $typeItems);
}
} else {
if (isset($tables[$contentType])) {
$items = $this->getModerationItemsByType($contentType, $tables[$contentType], $limit, $offset);
}
}
// Sort by upload date
usort($items, function($a, $b) {
return strtotime($b['uploaded_at']) - strtotime($a['uploaded_at']);
});
return [
'success' => true,
'items' => array_slice($items, 0, $limit),
'total' => count($items)
];
} catch (Exception $e) {
$this->logger->error('Failed to get moderation queue', [
'content_type' => $contentType,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Moderate content
* @param string $contentType Content type
* @param string $contentId Content ID
* @param string $action Action (approve, reject, delete)
* @param string $reason Reason
* @return array Result
*/
public function moderateContent($contentType, $contentId, $action, $reason = '')
{
try {
$tables = [
'video' => 'db_videofiles',
'image' => 'db_imagefiles',
'audio' => 'db_audiofiles',
'document' => 'db_documentfiles'
];
if (!isset($tables[$contentType])) {
return [
'success' => false,
'error' => 'Invalid content type'
];
}
$table = $tables[$contentType];
$updateData = ['moderated_at' => date('Y-m-d H:i:s')];
switch ($action) {
case 'approve':
$updateData['file_active'] = 1;
$updateData['moderation_status'] = 'approved';
break;
case 'reject':
$updateData['file_active'] = 0;
$updateData['moderation_status'] = 'rejected';
$updateData['moderation_reason'] = $reason;
break;
case 'delete':
$updateData['file_active'] = 0;
$updateData['moderation_status'] = 'deleted';
$updateData['moderation_reason'] = $reason;
break;
default:
return [
'success' => false,
'error' => 'Invalid action'
];
}
$this->db->doUpdate($table, 'file_key', $updateData, $contentId);
// Log admin action
$this->logAdminAction('content_moderation', [
'content_type' => $contentType,
'content_id' => $contentId,
'action' => $action,
'reason' => $reason
]);
return [
'success' => true,
'message' => "Content {$action}d successfully"
];
} catch (Exception $e) {
$this->logger->error('Failed to moderate content', [
'content_type' => $contentType,
'content_id' => $contentId,
'action' => $action,
'error' => $e->getMessage()
]);
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Get system health status
* @return array System health
*/
public function getSystemHealth()
{
try {
$health = [
'overall_status' => 'healthy',
'components' => [],
'issues' => []
];
// Check database
$dbHealth = $this->checkDatabaseHealth();
$health['components']['database'] = $dbHealth;
// Check queue system
$queueManager = new VQueueManager();
$queueHealth = $queueManager->healthCheck();
$health['components']['queue'] = $queueHealth;
// Check file system
$fsHealth = $this->checkFileSystemHealth();
$health['components']['filesystem'] = $fsHealth;
// Check external services
$extHealth = $this->checkExternalServices();
$health['components']['external'] = $extHealth;
// Determine overall status
foreach ($health['components'] as $component => $status) {
if ($status['status'] === 'unhealthy') {
$health['overall_status'] = 'unhealthy';
$health['issues'][] = "{$component}: " . implode(', ', $status['issues'] ?? []);
} elseif ($status['status'] === 'warning' && $health['overall_status'] === 'healthy') {
$health['overall_status'] = 'warning';
}
}
return [
'success' => true,
'health' => $health,
'checked_at' => date('Y-m-d H:i:s')
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
/**
* Private helper methods
*/
private function getTimeFilter($period)
{
switch ($period) {
case 'today':
return "DATE(created_at) = CURDATE()";
case 'week':
return "created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)";
case 'month':
return "created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)";
case 'year':
return "created_at >= DATE_SUB(NOW(), INTERVAL 365 DAY)";
default:
return "created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)";
}
}
private function getOverviewStats($timeFilter)
{
$stats = [];
// Total users
$query = "SELECT COUNT(*) as count FROM db_accountuser WHERE usr_deleted = 0";
$result = $this->db->doQuery($query);
$stats['total_users'] = $this->db->doFetch($result)['count'];
// New users
$query = "SELECT COUNT(*) as count FROM db_accountuser WHERE {$timeFilter}";
$result = $this->db->doQuery($query);
$stats['new_users'] = $this->db->doFetch($result)['count'];
// Total videos
$query = "SELECT COUNT(*) as count FROM db_videofiles WHERE file_active = 1";
$result = $this->db->doQuery($query);
$stats['total_videos'] = $this->db->doFetch($result)['count'];
// New videos
$query = "SELECT COUNT(*) as count FROM db_videofiles WHERE {$timeFilter}";
$result = $this->db->doQuery($query);
$stats['new_videos'] = $this->db->doFetch($result)['count'];
return $stats;
}
private function getUserStats($timeFilter)
{
$stats = [];
// User registrations by day
$query = "SELECT DATE(usr_joindate) as date, COUNT(*) as count
FROM db_accountuser
WHERE {$timeFilter}
GROUP BY DATE(usr_joindate)
ORDER BY date DESC";
$result = $this->db->doQuery($query);
$registrations = [];
while ($row = $this->db->doFetch($result)) {
$registrations[] = $row;
}
$stats['registrations_by_day'] = $registrations;
// User roles distribution
$query = "SELECT usr_role, COUNT(*) as count
FROM db_accountuser
WHERE usr_deleted = 0
GROUP BY usr_role";
$result = $this->db->doQuery($query);
$roles = [];
while ($row = $this->db->doFetch($result)) {
$roles[] = $row;
}
$stats['roles_distribution'] = $roles;
return $stats;
}
private function getContentStats($timeFilter)
{
$stats = [];
// Content uploads by type
$contentTypes = [
'videos' => 'db_videofiles',
'images' => 'db_imagefiles',
'audio' => 'db_audiofiles',
'documents' => 'db_documentfiles'
];
foreach ($contentTypes as $type => $table) {
$query = "SELECT COUNT(*) as count FROM {$table} WHERE {$timeFilter}";
$result = $this->db->doQuery($query);
$stats["new_{$type}"] = $this->db->doFetch($result)['count'];
}
return $stats;
}
private function getEngagementStats($timeFilter)
{
$stats = [];
// Video views
$query = "SELECT COUNT(*) as count FROM db_video_views WHERE {$timeFilter}";
$result = $this->db->doQuery($query);
$stats['video_views'] = $this->db->doFetch($result)['count'];
// Comments
$query = "SELECT COUNT(*) as count FROM db_comments WHERE {$timeFilter}";
$result = $this->db->doQuery($query);
$stats['comments'] = $this->db->doFetch($result)['count'];
// Likes
$query = "SELECT COUNT(*) as count FROM db_votes WHERE vote_type = 'like' AND {$timeFilter}";
$result = $this->db->doQuery($query);
$stats['likes'] = $this->db->doFetch($result)['count'];
return $stats;
}
private function getSystemStats()
{
$stats = [];
// Queue statistics
$queueManager = new VQueueManager();
$stats['queue'] = $queueManager->getQueueStatistics();
// Disk usage
$stats['disk_usage'] = [
'total' => disk_total_space('.'),
'free' => disk_free_space('.'),
'used' => disk_total_space('.') - disk_free_space('.')
];
// Memory usage
$stats['memory_usage'] = [
'current' => memory_get_usage(true),
'peak' => memory_get_peak_usage(true)
];
return $stats;
}
private function getRecentActivity($limit)
{
$activities = [];
// Recent user registrations
$query = "SELECT 'user_registration' as type, usr_user as title, usr_joindate as created_at
FROM db_accountuser
ORDER BY usr_joindate DESC
LIMIT ?";
$result = $this->db->doQuery($query, [$limit / 4]);
while ($row = $this->db->doFetch($result)) {
$activities[] = $row;
}
// Recent video uploads
$query = "SELECT 'video_upload' as type, file_title as title, file_date as created_at
FROM db_videofiles
ORDER BY file_date DESC
LIMIT ?";
$result = $this->db->doQuery($query, [$limit / 4]);
while ($row = $this->db->doFetch($result)) {
$activities[] = $row;
}
// Sort by date
usort($activities, function($a, $b) {
return strtotime($b['created_at']) - strtotime($a['created_at']);
});
return array_slice($activities, 0, $limit);
}
private function formatUserData($row)
{
return [
'id' => $row['usr_id'],
'username' => $row['usr_user'],
'email' => $row['usr_email'],
'role' => $row['usr_role'],
'active' => (bool)$row['usr_active'],
'deleted' => (bool)$row['usr_deleted'],
'verified' => (bool)$row['usr_verified'],
'joined_at' => $row['usr_joindate'],
'last_login' => $row['usr_lastlogin'],
'login_count' => $row['usr_logins'],
'content_counts' => [
'videos' => $row['usr_v_count'],
'images' => $row['usr_i_count'],
'audio' => $row['usr_a_count'],
'documents' => $row['usr_d_count']
]
];
}
private function getUserById($userId)
{
$query = "SELECT * FROM db_accountuser WHERE usr_id = ?";
$result = $this->db->doQuery($query, [$userId]);
return $this->db->doFetch($result);
}
private function logAdminAction($action, $data)
{
$logData = [
'admin_id' => $_SESSION['user_id'] ?? null,
'action' => $action,
'data' => json_encode($data),
'ip_address' => $this->getRealIpAddress(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'created_at' => date('Y-m-d H:i:s')
];
$this->db->doInsert('db_admin_logs', $logData);
}
private function getRealIpAddress()
{
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
}
private function getModerationItemsByType($type, $table, $limit, $offset)
{
$query = "SELECT f.*, u.usr_user
FROM {$table} f
JOIN db_accountuser u ON f.usr_id = u.usr_id
WHERE f.moderation_status IS NULL OR f.moderation_status = 'pending'
ORDER BY f.file_date DESC
LIMIT ? OFFSET ?";
$result = $this->db->doQuery($query, [$limit, $offset]);
$items = [];
while ($row = $this->db->doFetch($result)) {
$items[] = [
'type' => $type,
'id' => $row['file_key'],
'title' => $row['file_title'],
'description' => $row['file_description'] ?? '',
'uploader' => $row['usr_user'],
'uploaded_at' => $row['file_date'],
'file_size' => $row['file_size'] ?? 0,
'status' => $row['moderation_status'] ?? 'pending'
];
}
return $items;
}
private function checkDatabaseHealth()
{
try {
$result = $this->db->doQuery("SELECT 1");
if ($result) {
return [
'status' => 'healthy',
'message' => 'Database connection working'
];
} else {
return [
'status' => 'unhealthy',
'issues' => ['Database query failed']
];
}
} catch (Exception $e) {
return [
'status' => 'unhealthy',
'issues' => ['Database connection failed: ' . $e->getMessage()]
];
}
}
private function checkFileSystemHealth()
{
$issues = [];
$directories = [
'f_data/temp',
'f_data/processed',
'f_data/media',
'logs'
];
foreach ($directories as $dir) {
$fullPath = _FPATH . $dir;
if (!is_dir($fullPath)) {
$issues[] = "Directory missing: {$dir}";
} elseif (!is_writable($fullPath)) {
$issues[] = "Directory not writable: {$dir}";
}
}
// Check disk space
$freeSpace = disk_free_space('.');
$totalSpace = disk_total_space('.');
$usagePercent = (($totalSpace - $freeSpace) / $totalSpace) * 100;
if ($usagePercent > 90) {
$issues[] = "Disk usage critical: {$usagePercent}%";
} elseif ($usagePercent > 80) {
$issues[] = "Disk usage high: {$usagePercent}%";
}
if (empty($issues)) {
return [
'status' => 'healthy',
'message' => 'File system healthy'
];
} else {
return [
'status' => count($issues) > 2 ? 'unhealthy' : 'warning',
'issues' => $issues
];
}
}
private function checkExternalServices()
{
$issues = [];
// Check FFmpeg
$ffmpegOutput = shell_exec('ffmpeg -version 2>&1');
if (strpos($ffmpegOutput, 'ffmpeg version') === false) {
$issues[] = 'FFmpeg not available';
}
// Check Redis (optional)
try {
$redis = VRedis::getInstance();
if (!$redis->isConnected()) {
$issues[] = 'Redis not connected (using database fallback)';
}
} catch (Exception $e) {
// Redis is optional, so this is just a warning
}
if (empty($issues)) {
return [
'status' => 'healthy',
'message' => 'External services healthy'
];
} else {
return [
'status' => 'warning',
'issues' => $issues
];
}
}
}