Files
Krystie 3e4cfcf683 feat: Add comprehensive analytics dashboard for creators
- Create detailed event tracking system (views, watch time, retention, traffic)
- Build channel analytics with daily aggregation
- Implement real-time stats caching
- Add traffic source detection and tracking
- Create audience demographics and retention tracking
- Build creator dashboard with Chart.js visualizations
- Implement automatic video analytics tracker JavaScript
- Add cron script for daily stats aggregation
- Document complete analytics system

Features:
- Channel overview (views, watch time, subscribers, engagement)
- Time series charts with multiple metrics
- Traffic sources breakdown (search, social, direct, etc.)
- Audience demographics (countries, devices, age ranges)
- Top videos by performance metric
- Audience retention graphs (minute-by-minute)
- Real-time viewer statistics
- Automatic tracking (no manual instrumentation)
- Privacy-compliant (hashed IPs, GDPR-ready)
- Production-ready with proper indexing
2026-03-30 17:34:18 -07:00

152 lines
4.9 KiB
PHP

<?php
/**
* Analytics API
*
* GET /api/analytics.php?action=overview&days=30
* GET /api/analytics.php?action=timeseries&metric=views&days=30
* GET /api/analytics.php?action=top_videos&metric=views&limit=10
* GET /api/analytics.php?action=demographics&days=30
* GET /api/analytics.php?action=traffic_sources&days=30
* GET /api/analytics.php?action=retention&video_id=X&days=30
* POST /api/analytics.php?action=track - Track event
*/
require_once dirname(__DIR__) . '/f_core/config.boot.php';
require_once dirname(__DIR__) . '/f_core/f_classes/class.analytics.php';
header('Content-Type: application/json');
session_start();
$current_user_id = $_SESSION['user_id'] ?? null;
$analytics = new VAnalytics();
$action = $_GET['action'] ?? 'overview';
$days = intval($_GET['days'] ?? 30);
// Track event (public endpoint)
if ($action === 'track') {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'error' => 'Method not allowed']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$video_id = $input['video_id'] ?? null;
$event_type = $input['event_type'] ?? 'view';
if (!$video_id) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Video ID required']);
exit;
}
$data = [
'usr_id' => $current_user_id,
'watch_duration' => $input['watch_duration'] ?? 0,
'completion_percentage' => $input['completion_percentage'] ?? 0,
'country_code' => $input['country_code'] ?? null
];
$result = $analytics->trackEvent($video_id, $event_type, $data);
// Track retention if provided
if (isset($input['minute'])) {
$analytics->trackRetention($video_id, $input['minute']);
}
// Track traffic source if provided
if (isset($input['source_type'])) {
// Get video owner
global $class_database;
$sql = "SELECT usr_id FROM db_videofiles WHERE video_id = ?";
$res = $class_database->execute($sql, [$video_id]);
if ($res && $row = $class_database->fetch($res)) {
$analytics->trackTrafficSource(
$video_id,
$row['usr_id'],
$input['source_type'],
$input['source_detail'] ?? ''
);
}
}
echo json_encode(['success' => (bool)$result]);
exit;
}
// All other actions require authentication
if (!$current_user_id) {
http_response_code(401);
echo json_encode(['success' => false, 'error' => 'Authentication required']);
exit;
}
// Handle analytics queries
switch ($action) {
case 'overview':
$data = $analytics->getChannelOverview($current_user_id, $days);
echo json_encode(['success' => true, 'data' => $data]);
break;
case 'timeseries':
$metric = $_GET['metric'] ?? 'views';
$data = $analytics->getChannelTimeSeries($current_user_id, $days, $metric);
echo json_encode(['success' => true, 'data' => $data]);
break;
case 'top_videos':
$metric = $_GET['metric'] ?? 'views';
$limit = intval($_GET['limit'] ?? 10);
$data = $analytics->getTopVideos($current_user_id, $days, $metric, $limit);
echo json_encode(['success' => true, 'data' => $data]);
break;
case 'demographics':
$data = $analytics->getDemographics($current_user_id, $days);
echo json_encode(['success' => true, 'data' => $data]);
break;
case 'traffic_sources':
$data = $analytics->getTrafficSources($current_user_id, $days);
echo json_encode(['success' => true, 'data' => $data]);
break;
case 'retention':
$video_id = $_GET['video_id'] ?? null;
if (!$video_id) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Video ID required']);
exit;
}
// Verify ownership
global $class_database;
$sql = "SELECT usr_id FROM db_videofiles WHERE video_id = ?";
$result = $class_database->execute($sql, [$video_id]);
if (!$result || $class_database->rowCount($result) == 0) {
http_response_code(404);
echo json_encode(['success' => false, 'error' => 'Video not found']);
exit;
}
$row = $class_database->fetch($result);
if ($row['usr_id'] != $current_user_id) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'Permission denied']);
exit;
}
$data = $analytics->getAudienceRetention($video_id, $days);
echo json_encode(['success' => true, 'data' => $data]);
break;
default:
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Invalid action']);
}