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,684 @@
<?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');
/**
* RESTful API System for Mobile Apps and Third-Party Integration
*/
class VAPI
{
private $db;
private $logger;
private $rbac;
private $version = 'v1';
private $rateLimiter;
// API response codes
const SUCCESS = 200;
const CREATED = 201;
const BAD_REQUEST = 400;
const UNAUTHORIZED = 401;
const FORBIDDEN = 403;
const NOT_FOUND = 404;
const METHOD_NOT_ALLOWED = 405;
const RATE_LIMITED = 429;
const SERVER_ERROR = 500;
public function __construct()
{
$this->db = VDatabase::getInstance();
$this->logger = VLogger::getInstance();
$this->rbac = VRBAC::getInstance();
$this->rateLimiter = VSecurity::getInstance();
}
/**
* Handle API request
* @param string $method HTTP method
* @param string $endpoint API endpoint
* @param array $data Request data
* @param array $headers Request headers
* @return array API response
*/
public function handleRequest($method, $endpoint, $data = [], $headers = [])
{
try {
// Set CORS headers
$this->setCORSHeaders();
// Handle preflight requests
if ($method === 'OPTIONS') {
return $this->response(['message' => 'OK'], self::SUCCESS);
}
// Rate limiting
$clientId = $this->getClientIdentifier($headers);
if (!$this->checkRateLimit($clientId, $endpoint)) {
return $this->response(['error' => 'Rate limit exceeded'], self::RATE_LIMITED);
}
// Authentication
$user = $this->authenticateRequest($headers);
// Route request
$response = $this->routeRequest($method, $endpoint, $data, $user);
// Log API request
$this->logAPIRequest($method, $endpoint, $user['id'] ?? null, $response['status']);
return $response;
} catch (Exception $e) {
$this->logger->error('API request failed', [
'method' => $method,
'endpoint' => $endpoint,
'error' => $e->getMessage()
]);
return $this->response(['error' => 'Internal server error'], self::SERVER_ERROR);
}
}
/**
* Route API request to appropriate handler
* @param string $method HTTP method
* @param string $endpoint API endpoint
* @param array $data Request data
* @param array|null $user Authenticated user
* @return array API response
*/
private function routeRequest($method, $endpoint, $data, $user)
{
$parts = explode('/', trim($endpoint, '/'));
$resource = $parts[0] ?? '';
$id = $parts[1] ?? null;
$action = $parts[2] ?? null;
switch ($resource) {
case 'auth':
return $this->handleAuth($method, $id, $data);
case 'videos':
return $this->handleVideos($method, $id, $action, $data, $user);
case 'users':
return $this->handleUsers($method, $id, $action, $data, $user);
case 'live':
return $this->handleLiveStreams($method, $id, $action, $data, $user);
case 'search':
return $this->handleSearch($method, $data, $user);
case 'upload':
return $this->handleUpload($method, $data, $user);
case 'analytics':
return $this->handleAnalytics($method, $id, $action, $data, $user);
default:
return $this->response(['error' => 'Endpoint not found'], self::NOT_FOUND);
}
}
/**
* Handle authentication endpoints
* @param string $method HTTP method
* @param string $action Action
* @param array $data Request data
* @return array API response
*/
private function handleAuth($method, $action, $data)
{
switch ($action) {
case 'login':
if ($method !== 'POST') {
return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED);
}
return $this->login($data);
case 'register':
if ($method !== 'POST') {
return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED);
}
return $this->register($data);
case 'refresh':
if ($method !== 'POST') {
return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED);
}
return $this->refreshToken($data);
case 'logout':
if ($method !== 'POST') {
return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED);
}
return $this->logout($data);
default:
return $this->response(['error' => 'Auth action not found'], self::NOT_FOUND);
}
}
/**
* Handle video endpoints
* @param string $method HTTP method
* @param string $id Video ID
* @param string $action Action
* @param array $data Request data
* @param array|null $user Authenticated user
* @return array API response
*/
private function handleVideos($method, $id, $action, $data, $user)
{
switch ($method) {
case 'GET':
if ($id) {
if ($action === 'comments') {
return $this->getVideoComments($id, $data);
} elseif ($action === 'related') {
return $this->getRelatedVideos($id, $data);
} else {
return $this->getVideo($id, $user);
}
} else {
return $this->getVideos($data, $user);
}
case 'POST':
if ($id && $action === 'like') {
return $this->likeVideo($id, $user);
} elseif ($id && $action === 'comment') {
return $this->commentVideo($id, $data, $user);
} else {
return $this->createVideo($data, $user);
}
case 'PUT':
if ($id) {
return $this->updateVideo($id, $data, $user);
}
break;
case 'DELETE':
if ($id) {
return $this->deleteVideo($id, $user);
}
break;
}
return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED);
}
/**
* Handle user endpoints
* @param string $method HTTP method
* @param string $id User ID
* @param string $action Action
* @param array $data Request data
* @param array|null $user Authenticated user
* @return array API response
*/
private function handleUsers($method, $id, $action, $data, $user)
{
switch ($method) {
case 'GET':
if ($id) {
if ($action === 'videos') {
return $this->getUserVideos($id, $data);
} elseif ($action === 'followers') {
return $this->getUserFollowers($id, $data);
} elseif ($action === 'following') {
return $this->getUserFollowing($id, $data);
} else {
return $this->getUser($id, $user);
}
} else {
return $this->getUsers($data, $user);
}
case 'POST':
if ($id && $action === 'follow') {
return $this->followUser($id, $user);
}
break;
case 'PUT':
if ($id === 'me' || ($user && $id == $user['id'])) {
return $this->updateProfile($data, $user);
}
break;
case 'DELETE':
if ($id && $action === 'follow') {
return $this->unfollowUser($id, $user);
}
break;
}
return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED);
}
/**
* Handle live stream endpoints
* @param string $method HTTP method
* @param string $id Stream ID
* @param string $action Action
* @param array $data Request data
* @param array|null $user Authenticated user
* @return array API response
*/
private function handleLiveStreams($method, $id, $action, $data, $user)
{
if (!$user) {
return $this->response(['error' => 'Authentication required'], self::UNAUTHORIZED);
}
switch ($method) {
case 'GET':
if ($id) {
return $this->getLiveStream($id, $user);
} else {
return $this->getLiveStreams($data, $user);
}
case 'POST':
if ($id && $action === 'start') {
return $this->startLiveStream($id, $user);
} elseif ($id && $action === 'stop') {
return $this->stopLiveStream($id, $user);
} else {
return $this->createLiveStream($data, $user);
}
case 'PUT':
if ($id) {
return $this->updateLiveStream($id, $data, $user);
}
break;
case 'DELETE':
if ($id) {
return $this->deleteLiveStream($id, $user);
}
break;
}
return $this->response(['error' => 'Method not allowed'], self::METHOD_NOT_ALLOWED);
}
/**
* Authenticate API request
* @param array $headers Request headers
* @return array|null User data or null if not authenticated
*/
private function authenticateRequest($headers)
{
$authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? '';
if (empty($authHeader)) {
return null;
}
// Support Bearer token format
if (strpos($authHeader, 'Bearer ') === 0) {
$token = substr($authHeader, 7);
return $this->validateJWTToken($token);
}
// Support API key format
if (strpos($authHeader, 'ApiKey ') === 0) {
$apiKey = substr($authHeader, 7);
return $this->validateAPIKey($apiKey);
}
return null;
}
/**
* Validate JWT token
* @param string $token JWT token
* @return array|null User data or null if invalid
*/
private function validateJWTToken($token)
{
try {
// Simple JWT validation (in production, use a proper JWT library)
$parts = explode('.', $token);
if (count($parts) !== 3) {
return null;
}
$payload = json_decode(base64_decode($parts[1]), true);
if (!$payload || !isset($payload['user_id']) || $payload['exp'] < time()) {
return null;
}
// Get user from database
$query = "SELECT usr_id, usr_user, usr_email, usr_dname FROM db_accountuser WHERE usr_id = ? AND usr_status = 'active'";
$result = $this->db->doQuery($query, [$payload['user_id']]);
$user = $this->db->doFetch($result);
if ($user) {
return [
'id' => $user['usr_id'],
'username' => $user['usr_user'],
'email' => $user['usr_email'],
'display_name' => $user['usr_dname']
];
}
return null;
} catch (Exception $e) {
$this->logger->error('JWT validation failed', ['error' => $e->getMessage()]);
return null;
}
}
/**
* Validate API key
* @param string $apiKey API key
* @return array|null User data or null if invalid
*/
private function validateAPIKey($apiKey)
{
try {
$query = "SELECT ak.user_id, ak.permissions, au.usr_user, au.usr_email, au.usr_dname
FROM db_api_keys ak
JOIN db_accountuser au ON ak.user_id = au.usr_id
WHERE ak.api_key = ? AND ak.status = 'active' AND ak.expires_at > NOW()";
$result = $this->db->doQuery($query, [$apiKey]);
$keyData = $this->db->doFetch($result);
if ($keyData) {
return [
'id' => $keyData['user_id'],
'username' => $keyData['usr_user'],
'email' => $keyData['usr_email'],
'display_name' => $keyData['usr_dname'],
'api_permissions' => json_decode($keyData['permissions'], true) ?: []
];
}
return null;
} catch (Exception $e) {
$this->logger->error('API key validation failed', ['error' => $e->getMessage()]);
return null;
}
}
/**
* Generate JWT token
* @param array $user User data
* @return string JWT token
*/
private function generateJWTToken($user)
{
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
$payload = json_encode([
'user_id' => $user['id'],
'username' => $user['username'],
'iat' => time(),
'exp' => time() + (24 * 60 * 60) // 24 hours
]);
$headerEncoded = base64_encode($header);
$payloadEncoded = base64_encode($payload);
$signature = hash_hmac('sha256', $headerEncoded . '.' . $payloadEncoded, 'your-secret-key', true);
$signatureEncoded = base64_encode($signature);
return $headerEncoded . '.' . $payloadEncoded . '.' . $signatureEncoded;
}
/**
* Login user
* @param array $data Login data
* @return array API response
*/
private function login($data)
{
$username = $data['username'] ?? '';
$password = $data['password'] ?? '';
if (empty($username) || empty($password)) {
return $this->response(['error' => 'Username and password required'], self::BAD_REQUEST);
}
// Validate credentials
$query = "SELECT usr_id, usr_user, usr_email, usr_dname, usr_password
FROM db_accountuser
WHERE (usr_user = ? OR usr_email = ?) AND usr_status = 'active'";
$result = $this->db->doQuery($query, [$username, $username]);
$user = $this->db->doFetch($result);
if (!$user || !password_verify($password, $user['usr_password'])) {
return $this->response(['error' => 'Invalid credentials'], self::UNAUTHORIZED);
}
// Generate token
$userData = [
'id' => $user['usr_id'],
'username' => $user['usr_user'],
'email' => $user['usr_email'],
'display_name' => $user['usr_dname']
];
$token = $this->generateJWTToken($userData);
// Update last login
$this->db->doUpdate('db_accountuser', 'usr_id', [
'usr_lastlogin' => date('Y-m-d H:i:s')
], $user['usr_id']);
return $this->response([
'token' => $token,
'user' => $userData,
'expires_in' => 86400 // 24 hours
], self::SUCCESS);
}
/**
* Get videos
* @param array $params Query parameters
* @param array|null $user Authenticated user
* @return array API response
*/
private function getVideos($params, $user)
{
$page = max(1, (int)($params['page'] ?? 1));
$limit = min(50, max(1, (int)($params['limit'] ?? 20)));
$offset = ($page - 1) * $limit;
$category = $params['category'] ?? '';
$sort = $params['sort'] ?? 'recent';
$where = ["vf.file_type = 'video'", "vf.privacy = 'public'"];
$queryParams = [];
if ($category) {
$where[] = "vf.file_category = ?";
$queryParams[] = $category;
}
$orderBy = match($sort) {
'popular' => 'vf.file_views DESC',
'recent' => 'vf.upload_date DESC',
'rating' => 'vf.file_rating DESC',
default => 'vf.upload_date DESC'
};
$whereClause = implode(' AND ', $where);
$query = "SELECT vf.file_key, vf.file_title, vf.file_description, vf.file_views,
vf.file_rating, vf.upload_date, vf.file_duration, vf.file_size,
au.usr_user, au.usr_dname
FROM db_videofiles vf
JOIN db_accountuser au ON vf.usr_id = au.usr_id
WHERE {$whereClause}
ORDER BY {$orderBy}
LIMIT {$limit} OFFSET {$offset}";
$result = $this->db->doQuery($query, $queryParams);
$videos = [];
while ($row = $this->db->doFetch($result)) {
$videos[] = [
'id' => $row['file_key'],
'title' => $row['file_title'],
'description' => $row['file_description'],
'views' => (int)$row['file_views'],
'rating' => (float)$row['file_rating'],
'duration' => (int)$row['file_duration'],
'size' => (int)$row['file_size'],
'uploaded_at' => $row['upload_date'],
'uploader' => [
'username' => $row['usr_user'],
'display_name' => $row['usr_dname']
],
'thumbnail_url' => "/thumbnails/{$row['file_key']}_medium.jpg",
'video_url' => "/watch/{$row['file_key']}"
];
}
return $this->response([
'videos' => $videos,
'pagination' => [
'page' => $page,
'limit' => $limit,
'total' => $this->getVideoCount($where, $queryParams)
]
], self::SUCCESS);
}
/**
* Create API response
* @param array $data Response data
* @param int $status HTTP status code
* @return array API response
*/
private function response($data, $status = self::SUCCESS)
{
return [
'status' => $status,
'data' => $data,
'timestamp' => time(),
'version' => $this->version
];
}
/**
* Set CORS headers
*/
private function setCORSHeaders()
{
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
header('Access-Control-Max-Age: 86400');
}
/**
* Check rate limit
* @param string $clientId Client identifier
* @param string $endpoint Endpoint
* @return bool True if within limits
*/
private function checkRateLimit($clientId, $endpoint)
{
// Different limits for different endpoints
$limits = [
'auth' => ['requests' => 10, 'window' => 300], // 10 requests per 5 minutes
'upload' => ['requests' => 5, 'window' => 3600], // 5 uploads per hour
'default' => ['requests' => 100, 'window' => 3600] // 100 requests per hour
];
$endpointType = explode('/', $endpoint)[0] ?? 'default';
$limit = $limits[$endpointType] ?? $limits['default'];
return $this->rateLimiter->checkRateLimit(
"api_{$clientId}_{$endpointType}",
$limit['requests'],
$limit['window'],
"api_{$endpointType}"
);
}
/**
* Get client identifier
* @param array $headers Request headers
* @return string Client identifier
*/
private function getClientIdentifier($headers)
{
// Use API key if available, otherwise IP address
$authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? '';
if (strpos($authHeader, 'ApiKey ') === 0) {
return 'key_' . substr($authHeader, 7, 10);
}
return 'ip_' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
}
/**
* Log API request
* @param string $method HTTP method
* @param string $endpoint Endpoint
* @param int|null $userId User ID
* @param int $status Response status
*/
private function logAPIRequest($method, $endpoint, $userId, $status)
{
try {
$logData = [
'method' => $method,
'endpoint' => $endpoint,
'user_id' => $userId,
'status' => $status,
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
'created_at' => date('Y-m-d H:i:s')
];
$this->db->doInsert('db_api_logs', $logData);
} catch (Exception $e) {
$this->logger->error('Failed to log API request', [
'error' => $e->getMessage()
]);
}
}
/**
* Get video count for pagination
* @param array $where Where conditions
* @param array $params Query parameters
* @return int Total count
*/
private function getVideoCount($where, $params)
{
$whereClause = implode(' AND ', $where);
$query = "SELECT COUNT(*) as total FROM db_videofiles vf WHERE {$whereClause}";
$result = $this->db->doQuery($query, $params);
$row = $this->db->doFetch($result);
return (int)($row['total'] ?? 0);
}
}