Files
imf-sms-dashboard/web/api/send-now.php
T

123 lines
4.3 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/db.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
error_out('Method not allowed', 405);
}
// ─── POST /api/send-now.php ───────────────────────────────────────────────────
// Body: {
// "message": "...", -- required if template_id is omitted
// "template_id": 1, -- optional, overrides message
// "recipient_scope": "all", -- "all" (default) or "workshop"
// "workshop_session_id": 2 -- required when recipient_scope is "workshop"
// }
try {
require_api_key();
$body = json_body();
$customBody = trim((string) ($body['message'] ?? ''));
$templateId = isset($body['template_id']) ? (int) $body['template_id'] : null;
$scope = (string) ($body['recipient_scope'] ?? 'all');
$workshopId = isset($body['workshop_session_id']) ? (int) $body['workshop_session_id'] : null;
if ($customBody === '' && $templateId === null) {
error_out('Either message or template_id is required');
}
if (!in_array($scope, ['all', 'workshop'], true)) {
error_out('recipient_scope must be "all" or "workshop"');
}
if ($scope === 'workshop' && ($workshopId === null || $workshopId <= 0)) {
error_out('workshop_session_id is required when recipient_scope is "workshop"');
}
if ($customBody !== '' && mb_strlen($customBody) > 160) {
error_out('message must be 160 characters or fewer');
}
$db = db();
// Resolve message text from template if provided
$messageBody = $customBody;
if ($templateId !== null) {
$t = $db->prepare('SELECT body FROM sms_templates WHERE id = :id');
$t->execute([':id' => $templateId]);
$tpl = $t->fetch();
if (!$tpl) {
error_out('Template not found', 404);
}
$messageBody = $tpl['body'];
}
// Fetch recipients
if ($scope === 'all') {
$recipients = $db->query('SELECT id, mobile_number FROM attendees')->fetchAll();
} else {
$stmt = $db->prepare(
'SELECT a.id, a.mobile_number
FROM attendees a
JOIN registrations r ON r.attendee_id = a.id
WHERE r.workshop_session_id = :ws_id'
);
$stmt->execute([':ws_id' => $workshopId]);
$recipients = $stmt->fetchAll();
}
if (empty($recipients)) {
error_out('No recipients found for the selected scope');
}
$db->beginTransaction();
// Create the send job record (worker will update status once it has sent)
$jobStmt = $db->prepare(
'INSERT INTO send_jobs (template_id, custom_body, recipient_scope, workshop_session_id, status)
VALUES (:template_id, :custom_body, :scope, :ws_id, "pending")'
);
$jobStmt->execute([
':template_id' => $templateId,
':custom_body' => $customBody !== '' ? $customBody : null,
':scope' => $scope,
':ws_id' => $workshopId,
]);
$jobId = (int) $db->lastInsertId();
$db->commit();
// Dispatch to Python worker (synchronous — waits for all messages to send)
$ch = curl_init(WORKER_URL . '/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['job_id' => $jobId]),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlError || $httpCode !== 200) {
$db->prepare("UPDATE send_jobs SET status='failed' WHERE id = :id")
->execute([':id' => $jobId]);
error_out('SMS worker unavailable: ' . ($curlError ?: "HTTP $httpCode"), 503);
}
$result = json_decode((string) $response, true) ?? [];
json_out([
'job_id' => $jobId,
'sent' => (int) ($result['sent'] ?? 0),
'failed' => (int) ($result['failed'] ?? 0),
'recipients' => count($recipients),
]);
} catch (PDOException $e) {
if (isset($db) && $db->inTransaction()) {
$db->rollBack();
}
error_out('Database error: ' . $e->getMessage(), 500);
}