feat: Add complete Docker deployment with web-based setup wizard
Major additions: - Web-based setup wizard (setup.php, setup_wizard.php, setup-wizard.js) - Production Docker configuration (docker-compose.prod.yml, .env.production) - Database initialization SQL files (deploy/init_settings.sql) - Template builder system with drag-and-drop UI - Advanced features (OAuth, CDN, enhanced analytics, monetization) - Comprehensive documentation (deployment guides, quick start, feature docs) - Design system with accessibility and responsive layout - Deployment automation scripts (deploy.ps1, generate-secrets.ps1) Setup wizard allows customization of: - Platform name and branding - Domain configuration - Membership tiers and pricing - Admin credentials - Feature toggles Database includes 270+ tables for complete video streaming platform with advanced features for analytics, moderation, template building, and monetization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
218
f_core/f_classes/class.cdn.php
Normal file
218
f_core/f_classes/class.cdn.php
Normal file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
/**
|
||||
* EasyStream CDN Integration
|
||||
* Multi-CDN support: Cloudflare, AWS CloudFront, Bunny CDN
|
||||
* Version: 1.0
|
||||
*/
|
||||
|
||||
defined('_ISVALID') or header('Location: /error');
|
||||
|
||||
class VCDN {
|
||||
private static $db;
|
||||
private static $active_cdn = null;
|
||||
|
||||
public static function init() {
|
||||
self::$db = VDatabase::getInstance();
|
||||
self::loadActiveCDN();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load active CDN configuration
|
||||
*/
|
||||
private static function loadActiveCDN() {
|
||||
$sql = "SELECT * FROM db_cdn_config
|
||||
WHERE is_active = 1
|
||||
ORDER BY priority ASC
|
||||
LIMIT 1";
|
||||
|
||||
$result = self::$db->execute($sql);
|
||||
if ($result && $result->RecordCount() > 0) {
|
||||
self::$active_cdn = $result->FetchRow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CDN URL for file
|
||||
* @param string $file_path Original file path
|
||||
* @param string $file_type Type (video, image, audio, etc.)
|
||||
* @return string CDN URL or original path
|
||||
*/
|
||||
public static function getCDNUrl($file_path, $file_type = 'video') {
|
||||
self::init();
|
||||
|
||||
if (!self::$active_cdn || empty(self::$active_cdn['base_url'])) {
|
||||
return $file_path; // Return original if no CDN
|
||||
}
|
||||
|
||||
$base_url = rtrim(self::$active_cdn['base_url'], '/');
|
||||
$file_path = ltrim($file_path, '/');
|
||||
|
||||
return "$base_url/$file_path";
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge cache for specific file
|
||||
* @param string $file_path File path to purge
|
||||
* @return bool Success
|
||||
*/
|
||||
public static function purgeCache($file_path) {
|
||||
self::init();
|
||||
|
||||
if (!self::$active_cdn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$provider = self::$active_cdn['provider'];
|
||||
|
||||
switch ($provider) {
|
||||
case 'cloudflare':
|
||||
return self::purgeCloudflare($file_path);
|
||||
|
||||
case 'bunny':
|
||||
return self::purgeBunny($file_path);
|
||||
|
||||
case 'aws':
|
||||
return self::purgeAWS($file_path);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge Cloudflare cache
|
||||
*/
|
||||
private static function purgeCloudflare($file_path) {
|
||||
$zone_id = self::$active_cdn['zone_id'];
|
||||
$api_key = self::$active_cdn['api_key'];
|
||||
|
||||
if (empty($zone_id) || empty($api_key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$url = "https://api.cloudflare.com/client/v4/zones/$zone_id/purge_cache";
|
||||
|
||||
$cdn_url = self::getCDNUrl($file_path);
|
||||
|
||||
$data = json_encode(['files' => [$cdn_url]]);
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $api_key,
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($data)
|
||||
]);
|
||||
|
||||
$result = curl_exec($ch);
|
||||
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
return $status_code == 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge Bunny CDN cache
|
||||
*/
|
||||
private static function purgeBunny($file_path) {
|
||||
$api_key = self::$active_cdn['api_key'];
|
||||
$zone_id = self::$active_cdn['zone_id']; // Pull zone ID
|
||||
|
||||
if (empty($api_key) || empty($zone_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cdn_url = self::getCDNUrl($file_path);
|
||||
|
||||
$url = "https://api.bunny.net/pullzone/$zone_id/purgeCache";
|
||||
|
||||
$data = json_encode(['url' => $cdn_url]);
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'AccessKey: ' . $api_key,
|
||||
'Content-Type: application/json'
|
||||
]);
|
||||
|
||||
$result = curl_exec($ch);
|
||||
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
return $status_code == 200 || $status_code == 204;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge AWS CloudFront cache
|
||||
*/
|
||||
private static function purgeAWS($file_path) {
|
||||
// AWS CloudFront invalidation would require AWS SDK
|
||||
// For now, return false (implement if AWS SDK available)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track CDN statistics
|
||||
* @param string $file_key File key
|
||||
* @param int $bandwidth Bandwidth in MB
|
||||
* @param int $requests Number of requests
|
||||
*/
|
||||
public static function trackStats($file_key, $bandwidth, $requests) {
|
||||
self::init();
|
||||
|
||||
if (!self::$active_cdn) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file_key_safe = VDatabase::escape($file_key);
|
||||
$provider = VDatabase::escape(self::$active_cdn['provider']);
|
||||
$bandwidth = (float)$bandwidth;
|
||||
$requests = (int)$requests;
|
||||
$date = date('Y-m-d');
|
||||
|
||||
$sql = "INSERT INTO db_cdn_stats
|
||||
(file_key, cdn_provider, bandwidth_mb, requests, date)
|
||||
VALUES ('$file_key_safe', '$provider', $bandwidth, $requests, '$date')
|
||||
ON DUPLICATE KEY UPDATE
|
||||
bandwidth_mb = bandwidth_mb + $bandwidth,
|
||||
requests = requests + $requests";
|
||||
|
||||
self::$db->execute($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CDN statistics
|
||||
* @param string $file_key File key
|
||||
* @param string $date_from Start date
|
||||
* @param string $date_to End date
|
||||
* @return array Stats
|
||||
*/
|
||||
public static function getStats($file_key, $date_from, $date_to) {
|
||||
self::init();
|
||||
|
||||
$file_key_safe = VDatabase::escape($file_key);
|
||||
$date_from_safe = VDatabase::escape($date_from);
|
||||
$date_to_safe = VDatabase::escape($date_to);
|
||||
|
||||
$sql = "SELECT cdn_provider, SUM(bandwidth_mb) as total_bandwidth, SUM(requests) as total_requests
|
||||
FROM db_cdn_stats
|
||||
WHERE file_key = '$file_key_safe'
|
||||
AND date BETWEEN '$date_from_safe' AND '$date_to_safe'
|
||||
GROUP BY cdn_provider";
|
||||
|
||||
$result = self::$db->execute($sql);
|
||||
$stats = [];
|
||||
|
||||
if ($result) {
|
||||
while ($row = $result->FetchRow()) {
|
||||
$stats[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user