106 lines
3.2 KiB
PHP
106 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Dashboard\Controllers;
|
|
|
|
use App\Core\BaseApiController;
|
|
use App\Modules\Auth\Services\AuthService;
|
|
use App\Modules\Dashboard\Services\DashboardRealtimeService;
|
|
use CodeIgniter\HTTP\ResponseInterface;
|
|
|
|
/**
|
|
* Realtime Stream Controller
|
|
*
|
|
* Server-Sent Events (SSE) for live attendance updates.
|
|
* Role-aware: stream content is filtered by current user's roles (ADMIN / WALI_KELAS / GURU_MAPEL / ORANG_TUA).
|
|
*/
|
|
class RealtimeStreamController extends BaseApiController
|
|
{
|
|
protected DashboardRealtimeService $realtimeService;
|
|
protected AuthService $authService;
|
|
|
|
/** Stream interval in seconds */
|
|
protected int $intervalSeconds = 2;
|
|
|
|
/** Max stream duration in seconds */
|
|
protected int $timeoutSeconds = 60;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->realtimeService = new DashboardRealtimeService();
|
|
$this->authService = new AuthService();
|
|
}
|
|
|
|
/**
|
|
* GET /api/dashboard/stream
|
|
*
|
|
* SSE stream: every 2s check for new attendance, send event; timeout after 60s.
|
|
* Uses after_id for incremental feed. User context from session applied for role filtering.
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function index(): ResponseInterface
|
|
{
|
|
ignore_user_abort(true);
|
|
set_time_limit(0);
|
|
|
|
while (ob_get_level() > 0) {
|
|
ob_end_flush();
|
|
}
|
|
|
|
$this->response->setHeader('Content-Type', 'text/event-stream');
|
|
$this->response->setHeader('Cache-Control', 'no-cache');
|
|
$this->response->setHeader('Connection', 'keep-alive');
|
|
$this->response->setHeader('X-Accel-Buffering', 'no'); // nginx
|
|
$this->response->setBody('');
|
|
|
|
if (function_exists('apache_setenv')) {
|
|
@apache_setenv('no-gzip', '1');
|
|
}
|
|
@ini_set('output_buffering', 'off');
|
|
@ini_set('zlib.output_compression', false);
|
|
|
|
$this->response->sendHeaders();
|
|
|
|
$currentUser = $this->authService->currentUser();
|
|
$startTime = time();
|
|
$lastId = (int) ($this->request->getGet('after_id') ?? 0);
|
|
|
|
while (true) {
|
|
if (time() - $startTime >= $this->timeoutSeconds) {
|
|
$this->sendEvent('timeout', ['message' => 'Stream ended after ' . $this->timeoutSeconds . 's']);
|
|
break;
|
|
}
|
|
|
|
$rows = $this->realtimeService->getAttendanceSinceId($lastId, 50, $currentUser);
|
|
|
|
if (empty($rows)) {
|
|
$this->sendEvent('heartbeat', ['ts' => gmdate('Y-m-d\TH:i:s\Z')]);
|
|
} else {
|
|
foreach ($rows as $row) {
|
|
$this->sendEvent('attendance', $row);
|
|
$lastId = max($lastId, $row['id']);
|
|
}
|
|
}
|
|
|
|
flush();
|
|
|
|
sleep($this->intervalSeconds);
|
|
}
|
|
|
|
return $this->response;
|
|
}
|
|
|
|
/**
|
|
* Send one SSE event (event name + data line)
|
|
*
|
|
* @param string $event Event name
|
|
* @param array $data Data to send as JSON
|
|
*/
|
|
protected function sendEvent(string $event, array $data): void
|
|
{
|
|
echo 'event: ' . $event . "\n";
|
|
echo 'data: ' . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n\n";
|
|
flush();
|
|
}
|
|
}
|