Files

203 lines
6.6 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/db.php';
try {
match ($_SERVER['REQUEST_METHOD']) {
'GET' => handle_get(),
'POST' => handle_post(),
'PUT' => handle_put(),
'DELETE' => handle_delete(),
default => error_out('Method not allowed', 405),
};
} catch (PDOException $e) {
error_out('Database error: ' . $e->getMessage(), 500);
}
// ─── GET /api/schedule.php ────────────────────────────────────────────────────
function handle_get(): never
{
$rows = db()->query(
'SELECT sr.id, sr.mode, sr.offset_value, sr.offset_unit,
sr.specific_datetime, sr.created_at,
t.id AS template_id, t.name AS template_name
FROM scheduled_rules sr
JOIN sms_templates t ON t.id = sr.template_id
ORDER BY sr.created_at DESC'
)->fetchAll();
json_out(['rules' => $rows]);
}
// ─── POST /api/schedule.php ───────────────────────────────────────────────────
// Body: {
// "template_id": 1,
// "mode": "relative", -- or "specific"
// "offset_value": 30, -- required for relative
// "offset_unit": "minutes", -- required for relative (minutes|hours|days)
// "specific_datetime": "..." -- required for specific (ISO 8601 or MySQL datetime)
// }
function handle_post(): never
{
require_api_key();
$body = json_body();
$templateId = (int) ($body['template_id'] ?? 0);
$mode = (string) ($body['mode'] ?? '');
if ($templateId <= 0) {
error_out('template_id is required');
}
if (!in_array($mode, ['relative', 'specific'], true)) {
error_out('mode must be "relative" or "specific"');
}
$offsetValue = null;
$offsetUnit = null;
$specificDt = null;
if ($mode === 'relative') {
$offsetValue = (int) ($body['offset_value'] ?? 0);
$offsetUnit = (string) ($body['offset_unit'] ?? '');
if ($offsetValue <= 0) {
error_out('offset_value must be a positive integer');
}
if (!in_array($offsetUnit, ['minutes', 'hours', 'days'], true)) {
error_out('offset_unit must be "minutes", "hours", or "days"');
}
} else {
$specificDt = (string) ($body['specific_datetime'] ?? '');
if ($specificDt === '') {
error_out('specific_datetime is required when mode is "specific"');
}
if (strtotime($specificDt) === false) {
error_out('specific_datetime is not a valid date/time');
}
}
// Verify template exists
$check = db()->prepare('SELECT id FROM sms_templates WHERE id = :id');
$check->execute([':id' => $templateId]);
if (!$check->fetch()) {
error_out('Template not found', 404);
}
$stmt = db()->prepare(
'INSERT INTO scheduled_rules (template_id, mode, offset_value, offset_unit, specific_datetime)
VALUES (:template_id, :mode, :offset_value, :offset_unit, :specific_datetime)'
);
$stmt->execute([
':template_id' => $templateId,
':mode' => $mode,
':offset_value' => $offsetValue,
':offset_unit' => $offsetUnit,
':specific_datetime' => $specificDt,
]);
json_out(['id' => (int) db()->lastInsertId()], 201);
}
// ─── PUT /api/schedule.php?id=N ─────────────────────────────────────────────
// Body: same fields as POST
function handle_put(): never
{
require_api_key();
$id = (int) ($_GET['id'] ?? 0);
if ($id <= 0) {
error_out('Missing or invalid id');
}
$body = json_body();
$templateId = (int) ($body['template_id'] ?? 0);
$mode = (string) ($body['mode'] ?? '');
if ($templateId <= 0) {
error_out('template_id is required');
}
if (!in_array($mode, ['relative', 'specific'], true)) {
error_out('mode must be "relative" or "specific"');
}
$offsetValue = null;
$offsetUnit = null;
$specificDt = null;
if ($mode === 'relative') {
$offsetValue = (int) ($body['offset_value'] ?? 0);
$offsetUnit = (string) ($body['offset_unit'] ?? '');
if ($offsetValue <= 0) {
error_out('offset_value must be a positive integer');
}
if (!in_array($offsetUnit, ['minutes', 'hours', 'days'], true)) {
error_out('offset_unit must be "minutes", "hours", or "days"');
}
} else {
$specificDt = (string) ($body['specific_datetime'] ?? '');
if ($specificDt === '') {
error_out('specific_datetime is required when mode is "specific"');
}
if (strtotime($specificDt) === false) {
error_out('specific_datetime is not a valid date/time');
}
}
$db = db();
$check = $db->prepare('SELECT id FROM sms_templates WHERE id = :id');
$check->execute([':id' => $templateId]);
if (!$check->fetch()) {
error_out('Template not found', 404);
}
$stmt = $db->prepare(
'UPDATE scheduled_rules
SET template_id = :template_id, mode = :mode,
offset_value = :offset_value, offset_unit = :offset_unit,
specific_datetime = :specific_datetime
WHERE id = :id'
);
$stmt->execute([
':template_id' => $templateId,
':mode' => $mode,
':offset_value' => $offsetValue,
':offset_unit' => $offsetUnit,
':specific_datetime' => $specificDt,
':id' => $id,
]);
if ($stmt->rowCount() === 0) {
error_out('Rule not found', 404);
}
// Detach previous send_jobs so the updated rule fires fresh on the next
// scheduler tick (delivery_log history is preserved via the send_jobs FK).
$db->prepare('UPDATE send_jobs SET scheduled_rule_id = NULL WHERE scheduled_rule_id = :id')
->execute([':id' => $id]);
json_out(['updated' => true]);
}
// ─── DELETE /api/schedule.php?id=N ───────────────────────────────────────────
function handle_delete(): never
{
$id = (int) ($_GET['id'] ?? 0);
if ($id <= 0) {
error_out('Missing or invalid id');
}
$stmt = db()->prepare('DELETE FROM scheduled_rules WHERE id = :id');
$stmt->execute([':id' => $id]);
if ($stmt->rowCount() === 0) {
error_out('Rule not found', 404);
}
json_out(['deleted' => true]);
}