Files
ip-manager/webapp/config.php
2026-01-17 20:43:29 +00:00

314 lines
9.1 KiB
PHP

<?php
/**
* Geofeed Manager Configuration
*/
// Error reporting (disable in production)
error_reporting(E_ALL);
ini_set('display_errors', '0');
// Database configuration
define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
define('DB_NAME', getenv('DB_NAME') ?: 'geofeed_manager');
define('DB_USER', getenv('DB_USER') ?: 'root');
define('DB_PASS', getenv('DB_PASS') ?: '');
// Application settings
define('APP_NAME', 'Geofeed Manager');
define('APP_VERSION', '1.0.0');
define('ITEMS_PER_PAGE', 25);
// Session configuration
session_start();
// Database connection
function getDB() {
static $pdo = null;
if ($pdo === null) {
try {
$pdo = new PDO(
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
DB_USER,
DB_PASS,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die(json_encode(['error' => 'Database connection failed']));
}
}
return $pdo;
}
// CSRF Protection
function generateCSRFToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function validateCSRFToken($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
// JSON Response helper
function jsonResponse($data, $statusCode = 200) {
http_response_code($statusCode);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
// Input sanitization
function sanitizeInput($input) {
if (is_array($input)) {
return array_map('sanitizeInput', $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
// IP prefix validation
function isValidIpPrefix($prefix) {
if (strpos($prefix, '/') !== false) {
list($ip, $cidr) = explode('/', $prefix);
if (!is_numeric($cidr)) {
return false;
}
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return $cidr >= 0 && $cidr <= 32;
}
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
return $cidr >= 0 && $cidr <= 128;
}
return false;
}
return filter_var($prefix, FILTER_VALIDATE_IP) !== false;
}
// Country code validation
function isValidCountryCode($code) {
if (empty($code)) return true;
return preg_match('/^[A-Z]{2}$/i', $code);
}
// Region code validation (ISO 3166-2)
function isValidRegionCode($code) {
if (empty($code)) return true;
return preg_match('/^[A-Z]{2}-[A-Z0-9]{1,3}$/i', $code);
}
// Get a setting value from the database
function getSetting($db, $key, $default = null) {
static $cache = [];
if (isset($cache[$key])) {
return $cache[$key];
}
try {
$stmt = $db->prepare("SELECT setting_value FROM geofeed_settings WHERE setting_key = :key");
$stmt->execute([':key' => $key]);
$result = $stmt->fetch();
$cache[$key] = $result ? $result['setting_value'] : $default;
return $cache[$key];
} catch (Exception $e) {
return $default;
}
}
// Save a setting value to the database
function saveSetting($db, $key, $value) {
$stmt = $db->prepare("
INSERT INTO geofeed_settings (setting_key, setting_value)
VALUES (:key, :value)
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), updated_at = CURRENT_TIMESTAMP
");
$stmt->execute([':key' => $key, ':value' => $value]);
}
/**
* Queue a webhook notification with debouncing
* This will schedule a webhook to fire after a delay, consolidating multiple updates
*/
function queueWebhookNotification($db, $reason = 'manual', $entriesAffected = 1) {
// Check if webhooks are enabled
$enabled = getSetting($db, 'n8n_webhook_enabled', '0');
if ($enabled !== '1') {
return false;
}
$webhookUrl = getSetting($db, 'n8n_webhook_url', '');
if (empty($webhookUrl)) {
return false;
}
$delayMinutes = intval(getSetting($db, 'n8n_webhook_delay_minutes', '3'));
$scheduledFor = date('Y-m-d H:i:s', strtotime("+{$delayMinutes} minutes"));
// Check if there's already a pending webhook scheduled
$stmt = $db->prepare("
SELECT id, entries_affected FROM webhook_queue
WHERE status = 'pending' AND scheduled_for > NOW()
ORDER BY scheduled_for DESC LIMIT 1
");
$stmt->execute();
$existing = $stmt->fetch();
if ($existing) {
// Update existing pending webhook to consolidate and reschedule
$stmt = $db->prepare("
UPDATE webhook_queue
SET scheduled_for = :scheduled_for,
entries_affected = entries_affected + :entries,
trigger_reason = CONCAT(IFNULL(trigger_reason, ''), ', ', :reason)
WHERE id = :id
");
$stmt->execute([
':scheduled_for' => $scheduledFor,
':entries' => $entriesAffected,
':reason' => $reason,
':id' => $existing['id']
]);
return $existing['id'];
} else {
// Create new webhook queue entry
$stmt = $db->prepare("
INSERT INTO webhook_queue (webhook_type, trigger_reason, entries_affected, scheduled_for, status)
VALUES ('geofeed_update', :reason, :entries, :scheduled_for, 'pending')
");
$stmt->execute([
':reason' => $reason,
':entries' => $entriesAffected,
':scheduled_for' => $scheduledFor
]);
return $db->lastInsertId();
}
}
/**
* Process pending webhooks that are due
* This should be called by a cron job or the webhook processor endpoint
*/
function processWebhookQueue($db) {
$webhookUrl = getSetting($db, 'n8n_webhook_url', '');
if (empty($webhookUrl)) {
return ['processed' => 0, 'error' => 'No webhook URL configured'];
}
// Get pending webhooks that are due
$stmt = $db->prepare("
SELECT * FROM webhook_queue
WHERE status = 'pending' AND scheduled_for <= NOW()
ORDER BY scheduled_for ASC
LIMIT 10
");
$stmt->execute();
$webhooks = $stmt->fetchAll();
$processed = 0;
$results = [];
foreach ($webhooks as $webhook) {
// Mark as processing
$updateStmt = $db->prepare("UPDATE webhook_queue SET status = 'processing' WHERE id = :id");
$updateStmt->execute([':id' => $webhook['id']]);
// Send the webhook
$payload = [
'event' => 'geofeed_update',
'queue_id' => $webhook['id'],
'trigger_reason' => $webhook['trigger_reason'],
'entries_affected' => $webhook['entries_affected'],
'queued_at' => $webhook['queued_at'],
'timestamp' => date('c')
];
$result = sendWebhook($webhookUrl, $payload);
// Update status
$finalStatus = $result['success'] ? 'completed' : 'failed';
$updateStmt = $db->prepare("
UPDATE webhook_queue
SET status = :status, processed_at = NOW(), response_code = :code, response_body = :body
WHERE id = :id
");
$updateStmt->execute([
':status' => $finalStatus,
':code' => $result['http_code'],
':body' => substr($result['response'], 0, 1000),
':id' => $webhook['id']
]);
$processed++;
$results[] = [
'id' => $webhook['id'],
'success' => $result['success'],
'http_code' => $result['http_code']
];
}
return ['processed' => $processed, 'results' => $results];
}
/**
* Send a webhook to the configured URL
*/
function sendWebhook($url, $payload) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'User-Agent: Geofeed-Manager/1.0'
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return [
'success' => $httpCode >= 200 && $httpCode < 300,
'http_code' => $httpCode,
'response' => $response ?: $error,
'error' => $error
];
}
/**
* Trigger immediate webhook (bypasses queue for manual triggers)
*/
function triggerImmediateWebhook($db, $reason = 'manual_trigger') {
$webhookUrl = getSetting($db, 'n8n_webhook_url', '');
if (empty($webhookUrl)) {
return ['success' => false, 'error' => 'No webhook URL configured'];
}
$payload = [
'event' => 'geofeed_update',
'trigger_reason' => $reason,
'immediate' => true,
'timestamp' => date('c')
];
return sendWebhook($webhookUrl, $payload);
}