Files

267 lines
8.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Controllers;
use Core\Request;
use Core\Response;
use Core\Database;
use PDO;
class GatewayController
{
private string $configuredAppKey;
public function __construct()
{
$this->configuredAppKey = getenv('GATEWAY_APP_KEY') ?: 'uruk_gateway_secret_2026';
}
/**
* Authenticate gateway device app_key
*/
private function authenticate(string $appKey): bool
{
return hash_equals($this->configuredAppKey, $appKey);
}
/**
* GET /api/v1/gateway/pending-call or /pending-call.php
*/
public function pendingCall(Request $request): void
{
$deviceId = (string)$request->get('device_id');
$appKey = (string)$request->get('app_key');
if (!$this->authenticate($appKey) || empty($deviceId)) {
Response::json([
'task_id' => null,
'phone' => null,
'caller_id' => null,
'otp' => null,
'timeout_seconds' => null,
'error' => 'Unauthorized or missing device_id'
], 401);
return;
}
$pdo = Database::getConnection();
// Update device heartbeat
$this->heartbeat($pdo, $deviceId);
// Fetch oldest pending FLASH_CALL task
$stmt = $pdo->prepare('
SELECT id, target_phone, otp_code, timeout_seconds
FROM gateway_tasks
WHERE task_type = "FLASH_CALL"
AND (assigned_device_id = :dev OR assigned_device_id IS NULL)
AND status = "PENDING"
AND created_at >= NOW() - INTERVAL 60 SECOND
ORDER BY id ASC
LIMIT 1
');
$stmt->execute([':dev' => $deviceId]);
$task = $stmt->fetch(PDO::FETCH_ASSOC);
if ($task) {
// Lock task to this device
$upd = $pdo->prepare('UPDATE gateway_tasks SET status = "PROCESSING", assigned_device_id = :dev WHERE id = :id');
$upd->execute([':dev' => $deviceId, ':id' => $task['id']]);
Response::json([
'task_id' => (int)$task['id'],
'phone' => $task['target_phone'],
'caller_id' => null,
'otp' => $task['otp_code'],
'timeout_seconds' => (int)($task['timeout_seconds'] ?: 25),
]);
return;
}
Response::json([
'task_id' => null,
'phone' => null,
'caller_id' => null,
'otp' => null,
'timeout_seconds' => null,
]);
}
/**
* GET /api/v1/gateway/pending-sms or /pending-sms.php
*/
public function pendingSms(Request $request): void
{
$deviceId = (string)$request->get('device_id');
$appKey = (string)$request->get('app_key');
if (!$this->authenticate($appKey) || empty($deviceId)) {
Response::json([
'task_id' => null,
'phone' => null,
'caller_id' => null,
'otp' => null,
'timeout_seconds' => null,
'error' => 'Unauthorized or missing device_id'
], 401);
return;
}
$pdo = Database::getConnection();
$this->heartbeat($pdo, $deviceId);
$stmt = $pdo->prepare('
SELECT id, target_phone, otp_code, timeout_seconds
FROM gateway_tasks
WHERE task_type = "SMS"
AND (assigned_device_id = :dev OR assigned_device_id IS NULL)
AND status = "PENDING"
AND created_at >= NOW() - INTERVAL 120 SECOND
ORDER BY id ASC
LIMIT 1
');
$stmt->execute([':dev' => $deviceId]);
$task = $stmt->fetch(PDO::FETCH_ASSOC);
if ($task) {
$upd = $pdo->prepare('UPDATE gateway_tasks SET status = "PROCESSING", assigned_device_id = :dev WHERE id = :id');
$upd->execute([':dev' => $deviceId, ':id' => $task['id']]);
Response::json([
'task_id' => (int)$task['id'],
'phone' => $task['target_phone'],
'caller_id' => null,
'otp' => $task['otp_code'],
'timeout_seconds' => (int)($task['timeout_seconds'] ?: 30),
]);
return;
}
Response::json([
'task_id' => null,
'phone' => null,
'caller_id' => null,
'otp' => null,
'timeout_seconds' => null,
]);
}
/**
* POST /api/v1/gateway/call-done or /call-done.php
*/
public function callDone(Request $request): void
{
$taskId = (int)$request->get('task_id');
$deviceId = (string)$request->get('device_id');
$appKey = (string)$request->get('app_key');
$result = (string)$request->get('result');
if (!$this->authenticate($appKey)) {
Response::json(['success' => false, 'message' => 'Unauthorized'], 401);
return;
}
$pdo = Database::getConnection();
$newStatus = (strtoupper($result) === 'SUCCESS' || strtoupper($result) === 'DONE') ? 'COMPLETED' : 'FAILED';
$stmt = $pdo->prepare('
UPDATE gateway_tasks
SET status = :st, completed_at = NOW()
WHERE id = :id AND assigned_device_id = :dev
');
$stmt->execute([':st' => $newStatus, ':id' => $taskId, ':dev' => $deviceId]);
Response::json([
'success' => true,
'message' => "Call task {$taskId} recorded as {$newStatus}.",
'device_id' => $deviceId,
]);
}
/**
* POST /api/v1/gateway/sms-done or /sms-done.php
*/
public function smsDone(Request $request): void
{
$taskId = (int)$request->get('task_id');
$deviceId = (string)$request->get('device_id');
$appKey = (string)$request->get('app_key');
$result = (string)$request->get('result');
if (!$this->authenticate($appKey)) {
Response::json(['success' => false, 'message' => 'Unauthorized'], 401);
return;
}
$pdo = Database::getConnection();
$newStatus = (strtoupper($result) === 'SUCCESS' || strtoupper($result) === 'SENT') ? 'COMPLETED' : 'FAILED';
$stmt = $pdo->prepare('
UPDATE gateway_tasks
SET status = :st, completed_at = NOW()
WHERE id = :id AND assigned_device_id = :dev
');
$stmt->execute([':st' => $newStatus, ':id' => $taskId, ':dev' => $deviceId]);
Response::json([
'success' => true,
'message' => "SMS task {$taskId} recorded as {$newStatus}.",
'device_id' => $deviceId,
]);
}
/**
* POST /api/v1/gateway/register-device or /register-device.php
*/
public function registerDevice(Request $request): void
{
$deviceId = (string)$request->get('device_id');
$phoneNumber = (string)$request->get('phone_number');
$simSlot = (int)$request->get('sim_slot', 1);
$appKey = (string)$request->get('app_key');
if (!$this->authenticate($appKey) || empty($deviceId)) {
Response::json(['success' => false, 'message' => 'Unauthorized or missing device_id'], 401);
return;
}
$pdo = Database::getConnection();
$stmt = $pdo->prepare('
INSERT INTO gateway_devices (device_id, phone_number, sim_slot, status, last_heartbeat)
VALUES (:did, :phone, :slot, "ACTIVE", NOW())
ON DUPLICATE KEY UPDATE
phone_number = VALUES(phone_number),
sim_slot = VALUES(sim_slot),
status = "ACTIVE",
last_heartbeat = NOW()
');
$stmt->execute([
':did' => $deviceId,
':phone' => $phoneNumber,
':slot' => $simSlot,
]);
Response::json([
'success' => true,
'message' => 'Gateway device registered and active.',
'device_id' => $deviceId,
]);
}
private function heartbeat(PDO $pdo, string $deviceId): void
{
try {
$stmt = $pdo->prepare('
INSERT INTO gateway_devices (device_id, status, last_heartbeat)
VALUES (:did, "ACTIVE", NOW())
ON DUPLICATE KEY UPDATE status = "ACTIVE", last_heartbeat = NOW()
');
$stmt->execute([':did' => $deviceId]);
} catch (\Throwable $e) {
// Ignore heartbeat failures if table schema is pending
}
}
}