618 lines
25 KiB
PHP
618 lines
25 KiB
PHP
<?php
|
|
/**
|
|
* Nabeh API Front Controller
|
|
* Single entry point handling routing and application bootstrap.
|
|
*/
|
|
|
|
// 1. Boot the application (autoloader, env, errors)
|
|
require_once dirname(__DIR__) . '/app/bootstrap.php';
|
|
|
|
use App\Core\Request;
|
|
use App\Core\Response;
|
|
use App\Core\Router;
|
|
|
|
// 2. Initialize request and response objects
|
|
$request = new Request();
|
|
$response = new Response();
|
|
$router = new Router();
|
|
|
|
// 3. Define Global Middleware
|
|
$router->use(\App\Middlewares\SecurityMiddleware::class);
|
|
|
|
// 4. Define API Routes
|
|
// Serve landing.html landing page on root path
|
|
$router->get('/', function ($request, $response) {
|
|
$response->setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
$response->sendHeaders();
|
|
readfile(__DIR__ . '/landing.html');
|
|
exit;
|
|
});
|
|
|
|
// Serve index.html dashboard on app paths
|
|
$router->get('/login', function ($request, $response) {
|
|
$response->setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
$response->sendHeaders();
|
|
readfile(__DIR__ . '/index.html');
|
|
exit;
|
|
});
|
|
|
|
$router->get('/register', function ($request, $response) {
|
|
$response->setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
$response->sendHeaders();
|
|
readfile(__DIR__ . '/index.html');
|
|
exit;
|
|
});
|
|
|
|
$router->get('/dashboard', function ($request, $response) {
|
|
$response->setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
$response->sendHeaders();
|
|
readfile(__DIR__ . '/index.html');
|
|
exit;
|
|
});
|
|
|
|
// Route for serving the dynamic landing mockup image
|
|
$router->get('/landing_mockup.php', function ($request, $response) {
|
|
require __DIR__ . '/landing_mockup.php';
|
|
exit;
|
|
});
|
|
|
|
// Serve admin.html super admin panel on /admin path
|
|
$router->get('/admin', function ($request, $response) {
|
|
$response->setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
$response->sendHeaders();
|
|
readfile(__DIR__ . '/admin.html');
|
|
exit;
|
|
});
|
|
|
|
// Serve plan.html roadmap on /roadmap path
|
|
$router->get('/roadmap', function ($request, $response) {
|
|
$response->setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
$response->sendHeaders();
|
|
readfile(__DIR__ . '/plan.html');
|
|
exit;
|
|
});
|
|
|
|
// Health Check — no php_version or environment in production to avoid info disclosure
|
|
$router->get('/api/health', function ($request, $response) {
|
|
$response->json([
|
|
'status' => 'success',
|
|
'message' => 'Nabeh API is healthy',
|
|
'app_name' => getenv('APP_NAME') ?: 'Nabeh',
|
|
'time' => date('Y-m-d H:i:s')
|
|
]);
|
|
});
|
|
|
|
// Authentication Routes (Rate-limited: 5 attempts per 60 seconds per IP)
|
|
$router->post('/api/auth/register', [\App\Controllers\AuthController::class, 'register'], [\App\Middlewares\RateLimitMiddleware::class]);
|
|
$router->post('/api/auth/login', [\App\Controllers\AuthController::class, 'login'], [\App\Middlewares\RateLimitMiddleware::class]);
|
|
$router->get('/api/auth/me', [\App\Controllers\AuthController::class, 'me'], [\App\Middlewares\AuthMiddleware::class]);
|
|
|
|
// WhatsApp Gateway & Multi-Session Routes
|
|
$router->get('/api/whatsapp/status', [\App\Controllers\WhatsAppController::class, 'status'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/whatsapp/qr', [\App\Controllers\WhatsAppController::class, 'requestQr'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->post('/api/whatsapp/disconnect', [\App\Controllers\WhatsAppController::class, 'disconnect'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->get('/api/whatsapp/sessions', [\App\Controllers\WhatsAppController::class, 'listSessions'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/whatsapp/sessions', [\App\Controllers\WhatsAppController::class, 'createSession'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->delete('/api/whatsapp/sessions', [\App\Controllers\WhatsAppController::class, 'deleteSession'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/whatsapp/webhook', [\App\Controllers\WhatsAppController::class, 'webhook']); // No AuthMiddleware (Protected by WEBHOOK_SECRET internally)
|
|
|
|
// Meta Channel Integration & Multi-Session Routes
|
|
$router->get('/api/meta/sessions', [\App\Controllers\MetaWebhookController::class, 'listSessions'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/meta/sessions/connect', [\App\Controllers\MetaWebhookController::class, 'connectSession'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->delete('/api/meta/sessions', [\App\Controllers\MetaWebhookController::class, 'deleteSession'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->get('/api/webhooks/meta', [\App\Controllers\MetaWebhookController::class, 'verify']);
|
|
$router->post('/api/webhooks/meta', [\App\Controllers\MetaWebhookController::class, 'webhook']);
|
|
|
|
// Customer Service Agents (Staff) Routes
|
|
$router->get('/api/staff', [\App\Controllers\StaffController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/staff', [\App\Controllers\StaffController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->delete('/api/staff', [\App\Controllers\StaffController::class, 'delete'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->put('/api/staff/assign', [\App\Controllers\StaffController::class, 'assignSession'], [\App\Middlewares\AuthMiddleware::class]);
|
|
|
|
// Text and Voice OTP Verification Routes
|
|
$router->post('/api/otp/send', [\App\Controllers\OTPController::class, 'send'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
|
|
// Super Admin Routes
|
|
$router->get('/api/admin/stats', [\App\Controllers\SuperAdminController::class, 'getStats'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/admin/companies/subscribe', [\App\Controllers\SuperAdminController::class, 'subscribeCompany'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/admin/companies/approve-billing', [\App\Controllers\SuperAdminController::class, 'approveBilling'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/admin/export-chats', [\App\Controllers\SuperAdminController::class, 'exportChats'], [\App\Middlewares\AuthMiddleware::class]);
|
|
|
|
// Billing & Subscription Routes
|
|
$router->get('/api/plans', [\App\Controllers\BillingController::class, 'getPlans'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/billing/upgrade', [\App\Controllers\BillingController::class, 'upgrade'], [\App\Middlewares\AuthMiddleware::class]);
|
|
|
|
// Phase 4 & 5: CRM, Templates & Campaigns Routes
|
|
$router->get('/api/contacts', [\App\Controllers\ContactController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/contacts', [\App\Controllers\ContactController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
|
|
$router->get('/api/groups', [\App\Controllers\GroupController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/groups', [\App\Controllers\GroupController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->post('/api/groups/add', [\App\Controllers\GroupController::class, 'addContact'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->post('/api/groups/bulk-add', [\App\Controllers\GroupController::class, 'bulkAddContacts'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
|
|
$router->get('/api/templates', [\App\Controllers\TemplateController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/templates', [\App\Controllers\TemplateController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
|
|
$router->get('/api/campaigns', [\App\Controllers\CampaignController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/campaigns', [\App\Controllers\CampaignController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
|
|
$router->get('/api/chatbot/rules', [\App\Controllers\ChatbotController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/chatbot/rules',[\App\Controllers\ChatbotController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->post('/api/chatbot/generate-prompt-from-audio', [\App\Controllers\ChatbotController::class, 'generatePromptFromAudio'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
|
|
// Custom Integration Endpoints Routes (Phase 5)
|
|
$router->get('/api/endpoints', [\App\Controllers\EndpointController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
|
|
$router->post('/api/endpoints', [\App\Controllers\EndpointController::class, 'store'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->delete('/api/endpoints', [\App\Controllers\EndpointController::class, 'delete'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
|
|
// Salla Platform Integration Routes (Phase 6+)
|
|
$router->get('/api/integrations/salla/auth', [\App\Controllers\SallaController::class, 'auth']);
|
|
$router->get('/api/integrations/salla/callback', [\App\Controllers\SallaController::class, 'callback']);
|
|
$router->get('/api/integrations/salla/status', [\App\Controllers\SallaController::class, 'status'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->post('/api/integrations/salla/disconnect',[\App\Controllers\SallaController::class, 'disconnect'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->post('/api/webhooks/salla', [\App\Controllers\SallaController::class, 'webhook']);
|
|
|
|
// WooCommerce Store Integration Routes
|
|
$router->post('/api/integrations/woocommerce/connect', [\App\Controllers\WooCommerceController::class, 'connect'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->get('/api/integrations/woocommerce/status', [\App\Controllers\WooCommerceController::class, 'status'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->post('/api/integrations/woocommerce/disconnect', [\App\Controllers\WooCommerceController::class, 'disconnect'], [\App\Middlewares\AuthMiddleware::class, \App\Middlewares\SubscriptionMiddleware::class]);
|
|
$router->post('/api/webhooks/woocommerce', [\App\Controllers\WooCommerceController::class, 'webhook']);
|
|
|
|
|
|
// ============================================
|
|
// Siro Integration API Endpoints
|
|
// ============================================
|
|
|
|
// Siro Driver Info - Returns real-time driver data to Siro
|
|
$router->post('/api/siro/driver-info', function ($request, $response) {
|
|
$apiKey = getenv('NABEH_API_KEY');
|
|
$incomingKey = $request->getHeader('x-api-key') ?? '';
|
|
|
|
if (empty($apiKey) || $incomingKey !== $apiKey) {
|
|
$response->status(401)->json([
|
|
'status' => 'error',
|
|
'message' => 'Unauthorized'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$body = $request->getBody();
|
|
$phone = $body['phone'] ?? '';
|
|
|
|
if (empty($phone)) {
|
|
$response->status(400)->json([
|
|
'status' => 'error',
|
|
'message' => 'Missing phone number'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// Find driver OCR data
|
|
$hash = \App\Core\Security::blindIndex($phone);
|
|
$record = \App\Core\Database::selectOne(
|
|
"SELECT * FROM driver_ocr_data WHERE phone_hash = ? LIMIT 1",
|
|
[$hash]
|
|
);
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'data' => $record ? \App\Models\DriverOcrData::decryptRecord($record) : null
|
|
]);
|
|
});
|
|
|
|
// Siro Registration Status Check
|
|
$router->get('/api/siro/registration-status', function ($request, $response) {
|
|
$apiKey = getenv('NABEH_API_KEY');
|
|
$incomingKey = $request->getHeader('x-api-key') ?? '';
|
|
|
|
if (empty($apiKey) || $incomingKey !== $apiKey) {
|
|
$response->status(401)->json([
|
|
'status' => 'error',
|
|
'message' => 'Unauthorized'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$phone = $request->get('phone') ?? '';
|
|
|
|
if (empty($phone)) {
|
|
$response->status(400)->json([
|
|
'status' => 'error',
|
|
'message' => 'Missing phone parameter'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$hash = \App\Core\Security::blindIndex($phone);
|
|
$record = \App\Core\Database::selectOne(
|
|
"SELECT id, name, status, created_at, updated_at FROM driver_ocr_data WHERE phone_hash = ? LIMIT 1",
|
|
[$hash]
|
|
);
|
|
|
|
if (!$record) {
|
|
$response->json([
|
|
'status' => 'success',
|
|
'data' => null,
|
|
'message' => 'No registration found for this phone'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'data' => $record
|
|
]);
|
|
});
|
|
|
|
// Siro Webhook - Receives driver activation confirmations from Siro
|
|
$router->post('/api/siro/webhook', function ($request, $response) {
|
|
$apiKey = getenv('NABEH_API_KEY');
|
|
$incomingKey = $request->getHeader('x-api-key') ?? '';
|
|
|
|
if (empty($apiKey) || $incomingKey !== $apiKey) {
|
|
$response->status(401)->json([
|
|
'status' => 'error',
|
|
'message' => 'Unauthorized'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$body = $request->getBody();
|
|
$phone = $body['phone'] ?? '';
|
|
$syroDriverId = $body['driver_id'] ?? '';
|
|
$event = $body['event'] ?? '';
|
|
$status = $body['status'] ?? '';
|
|
|
|
if (empty($phone) || empty($event)) {
|
|
$response->status(400)->json([
|
|
'status' => 'error',
|
|
'message' => 'Missing required fields: phone, event'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
error_log("[Siro Webhook] Event: {$event}, Phone: {$phone}, DriverID: {$syroDriverId}, Status: {$status}");
|
|
|
|
$hash = \App\Core\Security::blindIndex($phone);
|
|
|
|
if ($event === 'driver_activated' && $status === 'actives') {
|
|
\App\Core\Database::execute(
|
|
"UPDATE driver_ocr_data SET status = 'registered', syro_driver_id = ? WHERE phone_hash = ?",
|
|
[$syroDriverId, $hash]
|
|
);
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'message' => 'Driver status updated'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
if ($event === 'driver_rejected') {
|
|
\App\Core\Database::execute(
|
|
"UPDATE driver_ocr_data SET status = 'rejected' WHERE phone_hash = ?",
|
|
[$hash]
|
|
);
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'message' => 'Driver rejected'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'message' => 'Event received'
|
|
]);
|
|
});
|
|
|
|
|
|
// Mock External API for Entaleq Driver Info (Used to fetch real-time driver data)
|
|
$router->post('/api/external/driver-info', function ($request, $response) {
|
|
$body = $request->getBody();
|
|
$phone = $body['phone'] ?? '';
|
|
|
|
if (empty($phone)) {
|
|
$response->status(400)->json([
|
|
'status' => 'error',
|
|
'message' => 'Missing phone number'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'data' => [
|
|
'name' => 'أحمد الشريف',
|
|
'phone' => $phone,
|
|
'role' => 'سائق',
|
|
'status' => 'نشط',
|
|
'balance' => 75.25,
|
|
'vehicle' => 'تويوتا كامري 2023',
|
|
'trips_count' => 1420
|
|
]
|
|
]);
|
|
});
|
|
|
|
// Mock External API for Entaleq Payment Verification (Used to demo automated slip validation)
|
|
$router->post('/api/external/verify-payment', function ($request, $response) {
|
|
$body = $request->getBody();
|
|
$phone = $body['phone'] ?? '';
|
|
$transactionId = $body['transaction_id'] ?? '';
|
|
$amount = $body['amount'] ?? '';
|
|
$method = $body['method'] ?? '';
|
|
|
|
if (empty($transactionId) || empty($amount)) {
|
|
$response->status(400)->json([
|
|
'status' => 'error',
|
|
'message' => 'Missing transaction_id or amount'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// Mock validation rules:
|
|
// If transaction_id contains 'fail' or starts with '9', mock verification failure
|
|
if (strpos(strtolower($transactionId), 'fail') !== false || (is_string($transactionId) && $transactionId[0] === '9')) {
|
|
$response->json([
|
|
'status' => 'failed',
|
|
'message' => 'هذا الرقم التعريفي للعملية تم استخدامه مسبقاً أو غير صحيح'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'message' => 'Payment verified and driver balance updated',
|
|
'data' => [
|
|
'driver_phone' => $phone,
|
|
'transaction_id' => $transactionId,
|
|
'amount' => $amount,
|
|
'method' => $method,
|
|
'new_balance' => 150.00
|
|
]
|
|
]);
|
|
});
|
|
|
|
|
|
// REST API for Entaleq Driver OCR Details (GET)
|
|
$router->get('/api/external/driver-ocr', function ($request, $response) {
|
|
$apiKey = getenv('ENTALEQ_API_KEY');
|
|
$incomingKey = $request->getHeader('x-api-key') ?? '';
|
|
|
|
if (empty($apiKey) || $incomingKey !== $apiKey) {
|
|
$response->status(401)->json([
|
|
'status' => 'error',
|
|
'message' => 'Unauthorized'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$phone = $request->get('phone') ?? '';
|
|
$companyId = $request->get('company_id') ?? '';
|
|
|
|
if (empty($phone)) {
|
|
$response->status(400)->json([
|
|
'status' => 'error',
|
|
'message' => 'Missing phone parameter'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
\App\Models\DriverOcrData::ensureTableExists();
|
|
$hash = \App\Core\Security::blindIndex($phone);
|
|
|
|
if ($companyId) {
|
|
$records = \App\Core\Database::select(
|
|
"SELECT * FROM driver_ocr_data WHERE company_id = ? AND phone_hash = ?",
|
|
[$companyId, $hash]
|
|
);
|
|
} else {
|
|
$records = \App\Core\Database::select(
|
|
"SELECT * FROM driver_ocr_data WHERE phone_hash = ?",
|
|
[$hash]
|
|
);
|
|
}
|
|
|
|
if (empty($records)) {
|
|
$response->status(404)->json([
|
|
'status' => 'error',
|
|
'message' => 'No driver records found for this phone number'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$decryptedRecords = [];
|
|
$host = ($request->getHeader('x-forwarded-proto') ?: 'http') . '://' . ($request->getHeader('host') ?: 'localhost');
|
|
|
|
foreach ($records as $record) {
|
|
$decrypted = \App\Models\DriverOcrData::decryptRecord($record);
|
|
if ($decrypted) {
|
|
// Include absolute paths for all document URLs
|
|
$urlFields = [
|
|
'id_front_url', 'id_back_url',
|
|
'driving_license_front_url', 'driving_license_back_url',
|
|
'vehicle_license_front_url', 'vehicle_license_back_url',
|
|
'criminal_record_url'
|
|
];
|
|
foreach ($urlFields as $field) {
|
|
if (!empty($decrypted[$field])) {
|
|
$decrypted[$field . '_absolute'] = $host . $decrypted[$field];
|
|
} else {
|
|
$decrypted[$field . '_absolute'] = null;
|
|
}
|
|
}
|
|
$decryptedRecords[] = $decrypted;
|
|
}
|
|
}
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'data' => $decryptedRecords
|
|
]);
|
|
});
|
|
|
|
// REST API to Mark Driver Registered (POST)
|
|
$router->post('/api/external/register-driver', function ($request, $response) {
|
|
$apiKey = getenv('ENTALEQ_API_KEY');
|
|
$incomingKey = $request->getHeader('x-api-key') ?? '';
|
|
|
|
if (empty($apiKey) || $incomingKey !== $apiKey) {
|
|
$response->status(401)->json([
|
|
'status' => 'error',
|
|
'message' => 'Unauthorized'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$phone = $request->get('phone') ?? '';
|
|
$companyId = $request->get('company_id') ?? '';
|
|
|
|
if (empty($phone)) {
|
|
$response->status(400)->json([
|
|
'status' => 'error',
|
|
'message' => 'Missing phone parameter'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
\App\Models\DriverOcrData::ensureTableExists();
|
|
$hash = \App\Core\Security::blindIndex($phone);
|
|
|
|
if ($companyId) {
|
|
$existing = \App\Core\Database::selectOne(
|
|
"SELECT id FROM driver_ocr_data WHERE company_id = ? AND phone_hash = ? LIMIT 1",
|
|
[$companyId, $hash]
|
|
);
|
|
} else {
|
|
$existing = \App\Core\Database::selectOne(
|
|
"SELECT id FROM driver_ocr_data WHERE phone_hash = ? LIMIT 1",
|
|
[$hash]
|
|
);
|
|
}
|
|
|
|
if (!$existing) {
|
|
$response->status(404)->json([
|
|
'status' => 'error',
|
|
'message' => 'Driver record not found'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
\App\Core\Database::execute(
|
|
"UPDATE driver_ocr_data SET status = 'registered' WHERE id = ?",
|
|
[$existing['id']]
|
|
);
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'message' => 'Driver status successfully updated to registered'
|
|
]);
|
|
});
|
|
|
|
|
|
// Cron job endpoint to send pending driver registration reminders
|
|
$router->get('/api/external/cron/send-reminders', function ($request, $response) {
|
|
$apiKey = getenv('ENTALEQ_API_KEY');
|
|
$incomingKey = $request->getHeader('x-api-key') ?? '';
|
|
|
|
if (empty($apiKey) || $incomingKey !== $apiKey) {
|
|
$response->status(401)->json([
|
|
'status' => 'error',
|
|
'message' => 'Unauthorized'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
\App\Models\DriverReminder::ensureTableExists();
|
|
$now = date('Y-m-d H:i:s');
|
|
$reminders = \App\Core\Database::select(
|
|
"SELECT * FROM driver_registration_reminders WHERE status = 'pending' AND scheduled_at <= ?",
|
|
[$now]
|
|
);
|
|
|
|
$processed = 0;
|
|
foreach ($reminders as $reminder) {
|
|
$companyId = $reminder['company_id'];
|
|
$phone = $reminder['phone'];
|
|
|
|
// Get company chatbot rule details
|
|
$rule = \App\Models\ChatbotRule::findActiveForRule($companyId);
|
|
$configuredGeminiKey = ($rule && !empty($rule['gemini_api_key'])) ? $rule['gemini_api_key'] : null;
|
|
$geminiKey = \App\Services\GeminiService::getGeminiApiKey($configuredGeminiKey);
|
|
|
|
$configuredElKey = ($rule && !empty($rule['elevenlabs_api_key'])) ? $rule['elevenlabs_api_key'] : null;
|
|
$elApiKey = \App\Services\GeminiService::getElevenLabsApiKey($configuredElKey);
|
|
|
|
$configuredVoiceId = ($rule && !empty($rule['elevenlabs_voice_id'])) ? $rule['elevenlabs_voice_id'] : null;
|
|
$elVoiceId = \App\Services\GeminiService::getElevenLabsVoiceId($configuredVoiceId);
|
|
|
|
// Fetch company WhatsApp session
|
|
$session = \App\Models\WhatsAppSession::findByCompany($companyId);
|
|
if (!$session || $session['status'] !== 'connected') {
|
|
error_log("Cron Reminder: WhatsApp session not connected for company {$companyId}");
|
|
continue;
|
|
}
|
|
|
|
// Try to get driver name from active conversation flow state
|
|
$driverName = '';
|
|
$state = \App\Models\ConversationState::findActive($companyId, $phone);
|
|
if ($state) {
|
|
$ctx = json_decode($state['context_data'] ?: '{}', true);
|
|
$driverName = $ctx['name'] ?? '';
|
|
}
|
|
|
|
$nameStr = $driverName ? " كابتن " . $driverName : " كابتن";
|
|
$reminderMsg = "أهلاً بك{$nameStr}، حابين نذكرك تكمل خطوات تسجيلك لتنضم لعائلة انطلق 🚖. بقية الأوراق كتير مهمة لنفعل حسابك ونبدأ سوا. بانتظار إرسالها!";
|
|
|
|
// Generate Audio voice note if key is present
|
|
$audioData = null;
|
|
if (!empty($geminiKey)) {
|
|
$audioData = \App\Services\GeminiService::generateAudioResponse(
|
|
$geminiKey,
|
|
"أنت روبوت خدمة العملاء لشركة انطلق، تتحدث باللهجة السورية الودودة.",
|
|
$reminderMsg,
|
|
'Puck',
|
|
$elApiKey ?: null,
|
|
$elVoiceId ?: null
|
|
);
|
|
}
|
|
|
|
try {
|
|
// 1. Send the text reminder
|
|
\App\Core\Flows\ConversationFlowEngine::sendReply($session, $phone, $reminderMsg);
|
|
|
|
// 2. Send the voice note reminder if generated successfully
|
|
if ($audioData && !empty($audioData['audio'])) {
|
|
\App\Core\Flows\ConversationFlowEngine::sendReply(
|
|
$session,
|
|
$phone,
|
|
'',
|
|
null,
|
|
$audioData['audio'],
|
|
$audioData['mimeType'] ?? 'audio/mp4'
|
|
);
|
|
}
|
|
|
|
// 3. Mark the reminder as sent
|
|
\App\Models\DriverReminder::update($reminder['id'], [
|
|
'status' => 'sent'
|
|
]);
|
|
$processed++;
|
|
} catch (\Exception $ex) {
|
|
error_log("Failed to process reminder ID {$reminder['id']}: " . $ex->getMessage());
|
|
}
|
|
}
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'processed_count' => $processed,
|
|
'total_pending_due' => count($reminders)
|
|
]);
|
|
});
|
|
|
|
|
|
// 4. Dispatch the request
|
|
$router->dispatch($request, $response);
|