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 { $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 { $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]); }