759 lines
22 KiB
PHP
759 lines
22 KiB
PHP
<?php
|
|
/**
|
|
* Geofeed Manager API
|
|
* RESTful API for managing geofeed entries
|
|
*/
|
|
|
|
require_once __DIR__ . '/config.php';
|
|
|
|
header('Content-Type: application/json');
|
|
header('X-Content-Type-Options: nosniff');
|
|
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
$action = $_GET['action'] ?? '';
|
|
|
|
// Handle preflight requests
|
|
if ($method === 'OPTIONS') {
|
|
http_response_code(204);
|
|
exit;
|
|
}
|
|
|
|
try {
|
|
$db = getDB();
|
|
|
|
switch ($action) {
|
|
case 'list':
|
|
handleList($db);
|
|
break;
|
|
|
|
case 'get':
|
|
handleGet($db);
|
|
break;
|
|
|
|
case 'create':
|
|
handleCreate($db);
|
|
break;
|
|
|
|
case 'update':
|
|
handleUpdate($db);
|
|
break;
|
|
|
|
case 'delete':
|
|
handleDelete($db);
|
|
break;
|
|
|
|
case 'export':
|
|
handleExport($db);
|
|
break;
|
|
|
|
case 'stats':
|
|
handleStats($db);
|
|
break;
|
|
|
|
case 'search':
|
|
handleSearch($db);
|
|
break;
|
|
|
|
case 'import':
|
|
handleImport($db);
|
|
break;
|
|
|
|
case 'import_url':
|
|
handleImportUrl($db);
|
|
break;
|
|
|
|
case 'clear_all':
|
|
handleClearAll($db);
|
|
break;
|
|
|
|
default:
|
|
jsonResponse(['error' => 'Invalid action'], 400);
|
|
}
|
|
} catch (Exception $e) {
|
|
jsonResponse(['error' => $e->getMessage()], 500);
|
|
}
|
|
|
|
/**
|
|
* List entries with pagination and filtering
|
|
*/
|
|
function handleList($db) {
|
|
$page = max(1, intval($_GET['page'] ?? 1));
|
|
$limit = min(100, max(10, intval($_GET['limit'] ?? ITEMS_PER_PAGE)));
|
|
$offset = ($page - 1) * $limit;
|
|
|
|
$where = ['1=1'];
|
|
$params = [];
|
|
|
|
// Filtering
|
|
if (!empty($_GET['country'])) {
|
|
$where[] = 'country_code = :country';
|
|
$params[':country'] = strtoupper($_GET['country']);
|
|
}
|
|
|
|
if (!empty($_GET['search'])) {
|
|
$where[] = '(ip_prefix LIKE :search OR city LIKE :search2 OR region_code LIKE :search3)';
|
|
$searchTerm = '%' . $_GET['search'] . '%';
|
|
$params[':search'] = $searchTerm;
|
|
$params[':search2'] = $searchTerm;
|
|
$params[':search3'] = $searchTerm;
|
|
}
|
|
|
|
$whereClause = implode(' AND ', $where);
|
|
|
|
// Get total count
|
|
$countStmt = $db->prepare("SELECT COUNT(*) as total FROM geofeed_entries WHERE $whereClause");
|
|
$countStmt->execute($params);
|
|
$total = $countStmt->fetch()['total'];
|
|
|
|
// Get entries
|
|
$sql = "SELECT * FROM geofeed_entries WHERE $whereClause ORDER BY created_at DESC LIMIT :limit OFFSET :offset";
|
|
$stmt = $db->prepare($sql);
|
|
|
|
foreach ($params as $key => $value) {
|
|
$stmt->bindValue($key, $value);
|
|
}
|
|
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
|
|
$entries = $stmt->fetchAll();
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'data' => $entries,
|
|
'pagination' => [
|
|
'page' => $page,
|
|
'limit' => $limit,
|
|
'total' => $total,
|
|
'pages' => ceil($total / $limit)
|
|
]
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get single entry
|
|
*/
|
|
function handleGet($db) {
|
|
$id = intval($_GET['id'] ?? 0);
|
|
|
|
if (!$id) {
|
|
jsonResponse(['error' => 'Invalid ID'], 400);
|
|
}
|
|
|
|
$stmt = $db->prepare("SELECT * FROM geofeed_entries WHERE id = :id");
|
|
$stmt->execute([':id' => $id]);
|
|
$entry = $stmt->fetch();
|
|
|
|
if (!$entry) {
|
|
jsonResponse(['error' => 'Entry not found'], 404);
|
|
}
|
|
|
|
jsonResponse(['success' => true, 'data' => $entry]);
|
|
}
|
|
|
|
/**
|
|
* Create new entry
|
|
*/
|
|
function handleCreate($db) {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['error' => 'Method not allowed'], 405);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
// Validate CSRF
|
|
if (!validateCSRFToken($input['csrf_token'] ?? '')) {
|
|
jsonResponse(['error' => 'Invalid CSRF token'], 403);
|
|
}
|
|
|
|
// Validate required fields
|
|
if (empty($input['ip_prefix'])) {
|
|
jsonResponse(['error' => 'IP prefix is required'], 400);
|
|
}
|
|
|
|
if (!isValidIpPrefix($input['ip_prefix'])) {
|
|
jsonResponse(['error' => 'Invalid IP prefix format'], 400);
|
|
}
|
|
|
|
// Validate optional fields
|
|
$countryCode = strtoupper(trim($input['country_code'] ?? ''));
|
|
if (!empty($countryCode) && !isValidCountryCode($countryCode)) {
|
|
jsonResponse(['error' => 'Invalid country code (must be 2 letters)'], 400);
|
|
}
|
|
|
|
$regionCode = strtoupper(trim($input['region_code'] ?? ''));
|
|
if (!empty($regionCode) && !isValidRegionCode($regionCode)) {
|
|
jsonResponse(['error' => 'Invalid region code (format: XX-YYY)'], 400);
|
|
}
|
|
|
|
// Check for duplicate
|
|
$checkStmt = $db->prepare("SELECT id FROM geofeed_entries WHERE ip_prefix = :prefix");
|
|
$checkStmt->execute([':prefix' => $input['ip_prefix']]);
|
|
if ($checkStmt->fetch()) {
|
|
jsonResponse(['error' => 'An entry with this IP prefix already exists'], 409);
|
|
}
|
|
|
|
// Insert entry
|
|
$stmt = $db->prepare("
|
|
INSERT INTO geofeed_entries (ip_prefix, country_code, region_code, city, postal_code, notes)
|
|
VALUES (:ip_prefix, :country_code, :region_code, :city, :postal_code, :notes)
|
|
");
|
|
|
|
$stmt->execute([
|
|
':ip_prefix' => trim($input['ip_prefix']),
|
|
':country_code' => $countryCode ?: null,
|
|
':region_code' => $regionCode ?: null,
|
|
':city' => trim($input['city'] ?? '') ?: null,
|
|
':postal_code' => trim($input['postal_code'] ?? '') ?: null,
|
|
':notes' => trim($input['notes'] ?? '') ?: null
|
|
]);
|
|
|
|
$id = $db->lastInsertId();
|
|
|
|
// Log the action
|
|
logAction($db, $id, 'INSERT', null, $input);
|
|
|
|
jsonResponse(['success' => true, 'id' => $id, 'message' => 'Entry created successfully'], 201);
|
|
}
|
|
|
|
/**
|
|
* Update existing entry
|
|
*/
|
|
function handleUpdate($db) {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['error' => 'Method not allowed'], 405);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$id = intval($input['id'] ?? 0);
|
|
|
|
if (!$id) {
|
|
jsonResponse(['error' => 'Invalid ID'], 400);
|
|
}
|
|
|
|
// Validate CSRF
|
|
if (!validateCSRFToken($input['csrf_token'] ?? '')) {
|
|
jsonResponse(['error' => 'Invalid CSRF token'], 403);
|
|
}
|
|
|
|
// Get existing entry
|
|
$checkStmt = $db->prepare("SELECT * FROM geofeed_entries WHERE id = :id");
|
|
$checkStmt->execute([':id' => $id]);
|
|
$oldEntry = $checkStmt->fetch();
|
|
|
|
if (!$oldEntry) {
|
|
jsonResponse(['error' => 'Entry not found'], 404);
|
|
}
|
|
|
|
// Validate fields
|
|
if (empty($input['ip_prefix'])) {
|
|
jsonResponse(['error' => 'IP prefix is required'], 400);
|
|
}
|
|
|
|
if (!isValidIpPrefix($input['ip_prefix'])) {
|
|
jsonResponse(['error' => 'Invalid IP prefix format'], 400);
|
|
}
|
|
|
|
$countryCode = strtoupper(trim($input['country_code'] ?? ''));
|
|
if (!empty($countryCode) && !isValidCountryCode($countryCode)) {
|
|
jsonResponse(['error' => 'Invalid country code'], 400);
|
|
}
|
|
|
|
$regionCode = strtoupper(trim($input['region_code'] ?? ''));
|
|
if (!empty($regionCode) && !isValidRegionCode($regionCode)) {
|
|
jsonResponse(['error' => 'Invalid region code'], 400);
|
|
}
|
|
|
|
// Check for duplicate (excluding current entry)
|
|
$dupStmt = $db->prepare("SELECT id FROM geofeed_entries WHERE ip_prefix = :prefix AND id != :id");
|
|
$dupStmt->execute([':prefix' => $input['ip_prefix'], ':id' => $id]);
|
|
if ($dupStmt->fetch()) {
|
|
jsonResponse(['error' => 'Another entry with this IP prefix already exists'], 409);
|
|
}
|
|
|
|
// Update entry
|
|
$stmt = $db->prepare("
|
|
UPDATE geofeed_entries SET
|
|
ip_prefix = :ip_prefix,
|
|
country_code = :country_code,
|
|
region_code = :region_code,
|
|
city = :city,
|
|
postal_code = :postal_code,
|
|
notes = :notes
|
|
WHERE id = :id
|
|
");
|
|
|
|
$stmt->execute([
|
|
':id' => $id,
|
|
':ip_prefix' => trim($input['ip_prefix']),
|
|
':country_code' => $countryCode ?: null,
|
|
':region_code' => $regionCode ?: null,
|
|
':city' => trim($input['city'] ?? '') ?: null,
|
|
':postal_code' => trim($input['postal_code'] ?? '') ?: null,
|
|
':notes' => trim($input['notes'] ?? '') ?: null
|
|
]);
|
|
|
|
// Log the action
|
|
logAction($db, $id, 'UPDATE', $oldEntry, $input);
|
|
|
|
jsonResponse(['success' => true, 'message' => 'Entry updated successfully']);
|
|
}
|
|
|
|
/**
|
|
* Delete entry
|
|
*/
|
|
function handleDelete($db) {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['error' => 'Method not allowed'], 405);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$id = intval($input['id'] ?? 0);
|
|
|
|
if (!$id) {
|
|
jsonResponse(['error' => 'Invalid ID'], 400);
|
|
}
|
|
|
|
// Validate CSRF
|
|
if (!validateCSRFToken($input['csrf_token'] ?? '')) {
|
|
jsonResponse(['error' => 'Invalid CSRF token'], 403);
|
|
}
|
|
|
|
// Get existing entry for logging
|
|
$checkStmt = $db->prepare("SELECT * FROM geofeed_entries WHERE id = :id");
|
|
$checkStmt->execute([':id' => $id]);
|
|
$oldEntry = $checkStmt->fetch();
|
|
|
|
if (!$oldEntry) {
|
|
jsonResponse(['error' => 'Entry not found'], 404);
|
|
}
|
|
|
|
// Delete entry
|
|
$stmt = $db->prepare("DELETE FROM geofeed_entries WHERE id = :id");
|
|
$stmt->execute([':id' => $id]);
|
|
|
|
// Log the action
|
|
logAction($db, $id, 'DELETE', $oldEntry, null);
|
|
|
|
jsonResponse(['success' => true, 'message' => 'Entry deleted successfully']);
|
|
}
|
|
|
|
/**
|
|
* Export entries as CSV
|
|
*/
|
|
function handleExport($db) {
|
|
$stmt = $db->query("SELECT ip_prefix, country_code, region_code, city, postal_code FROM geofeed_entries ORDER BY ip_prefix");
|
|
$entries = $stmt->fetchAll();
|
|
|
|
// Build CSV content (RFC 8805 format)
|
|
$csv = "# Geofeed - Generated by Geofeed Manager\r\n";
|
|
$csv .= "# Format: ip_prefix,country_code,region_code,city,postal_code\r\n";
|
|
$csv .= "# Generated: " . date('c') . "\r\n";
|
|
|
|
foreach ($entries as $entry) {
|
|
$csv .= implode(',', [
|
|
$entry['ip_prefix'],
|
|
$entry['country_code'] ?? '',
|
|
$entry['region_code'] ?? '',
|
|
$entry['city'] ?? '',
|
|
$entry['postal_code'] ?? ''
|
|
]) . "\r\n";
|
|
}
|
|
|
|
// Return as downloadable or as JSON based on format param
|
|
if (($_GET['format'] ?? '') === 'download') {
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
header('Content-Disposition: attachment; filename="geofeed.csv"');
|
|
echo $csv;
|
|
exit;
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'csv' => $csv,
|
|
'count' => count($entries)
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get statistics
|
|
*/
|
|
function handleStats($db) {
|
|
$stats = [];
|
|
|
|
// Total entries
|
|
$stmt = $db->query("SELECT COUNT(*) as count FROM geofeed_entries");
|
|
$stats['total_entries'] = $stmt->fetch()['count'];
|
|
|
|
// Entries by country (top 10)
|
|
$stmt = $db->query("
|
|
SELECT country_code, COUNT(*) as count
|
|
FROM geofeed_entries
|
|
WHERE country_code IS NOT NULL AND country_code != ''
|
|
GROUP BY country_code
|
|
ORDER BY count DESC
|
|
LIMIT 10
|
|
");
|
|
$stats['by_country'] = $stmt->fetchAll();
|
|
|
|
// Recent changes
|
|
$stmt = $db->query("
|
|
SELECT action, COUNT(*) as count, DATE(changed_at) as date
|
|
FROM geofeed_audit_log
|
|
WHERE changed_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
|
GROUP BY action, DATE(changed_at)
|
|
ORDER BY date DESC
|
|
");
|
|
$stats['recent_changes'] = $stmt->fetchAll();
|
|
|
|
// IPv4 vs IPv6
|
|
$stmt = $db->query("
|
|
SELECT
|
|
SUM(CASE WHEN ip_prefix LIKE '%:%' THEN 1 ELSE 0 END) as ipv6,
|
|
SUM(CASE WHEN ip_prefix NOT LIKE '%:%' THEN 1 ELSE 0 END) as ipv4
|
|
FROM geofeed_entries
|
|
");
|
|
$stats['ip_versions'] = $stmt->fetch();
|
|
|
|
jsonResponse(['success' => true, 'data' => $stats]);
|
|
}
|
|
|
|
/**
|
|
* Search entries
|
|
*/
|
|
function handleSearch($db) {
|
|
$query = trim($_GET['q'] ?? '');
|
|
|
|
if (strlen($query) < 2) {
|
|
jsonResponse(['error' => 'Search query too short'], 400);
|
|
}
|
|
|
|
$searchTerm = '%' . $query . '%';
|
|
|
|
$stmt = $db->prepare("
|
|
SELECT * FROM geofeed_entries
|
|
WHERE ip_prefix LIKE :q1
|
|
OR city LIKE :q2
|
|
OR region_code LIKE :q3
|
|
OR country_code LIKE :q4
|
|
ORDER BY ip_prefix
|
|
LIMIT 50
|
|
");
|
|
|
|
$stmt->execute([
|
|
':q1' => $searchTerm,
|
|
':q2' => $searchTerm,
|
|
':q3' => $searchTerm,
|
|
':q4' => $searchTerm
|
|
]);
|
|
|
|
jsonResponse(['success' => true, 'data' => $stmt->fetchAll()]);
|
|
}
|
|
|
|
/**
|
|
* Import entries from parsed CSV data
|
|
*/
|
|
function handleImport($db) {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['error' => 'Method not allowed'], 405);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
// Validate CSRF
|
|
if (!validateCSRFToken($input['csrf_token'] ?? '')) {
|
|
jsonResponse(['error' => 'Invalid CSRF token'], 403);
|
|
}
|
|
|
|
$entries = $input['entries'] ?? [];
|
|
|
|
if (empty($entries)) {
|
|
jsonResponse(['error' => 'No entries provided'], 400);
|
|
}
|
|
|
|
$inserted = 0;
|
|
$updated = 0;
|
|
$failed = 0;
|
|
$errors = [];
|
|
|
|
$stmt = $db->prepare("
|
|
INSERT INTO geofeed_entries (ip_prefix, country_code, region_code, city, postal_code)
|
|
VALUES (:ip_prefix, :country_code, :region_code, :city, :postal_code)
|
|
ON DUPLICATE KEY UPDATE
|
|
country_code = VALUES(country_code),
|
|
region_code = VALUES(region_code),
|
|
city = VALUES(city),
|
|
postal_code = VALUES(postal_code),
|
|
updated_at = CURRENT_TIMESTAMP
|
|
");
|
|
|
|
$db->beginTransaction();
|
|
|
|
try {
|
|
foreach ($entries as $entry) {
|
|
$ipPrefix = trim($entry['ip_prefix'] ?? '');
|
|
|
|
if (empty($ipPrefix) || !isValidIpPrefix($ipPrefix)) {
|
|
$failed++;
|
|
continue;
|
|
}
|
|
|
|
$countryCode = strtoupper(trim($entry['country_code'] ?? ''));
|
|
if (!empty($countryCode) && !isValidCountryCode($countryCode)) {
|
|
$countryCode = null;
|
|
}
|
|
|
|
$regionCode = strtoupper(trim($entry['region_code'] ?? ''));
|
|
if (!empty($regionCode) && !isValidRegionCode($regionCode)) {
|
|
$regionCode = null;
|
|
}
|
|
|
|
try {
|
|
$stmt->execute([
|
|
':ip_prefix' => $ipPrefix,
|
|
':country_code' => $countryCode ?: null,
|
|
':region_code' => $regionCode ?: null,
|
|
':city' => trim($entry['city'] ?? '') ?: null,
|
|
':postal_code' => trim($entry['postal_code'] ?? '') ?: null
|
|
]);
|
|
|
|
if ($stmt->rowCount() === 1) {
|
|
$inserted++;
|
|
} elseif ($stmt->rowCount() === 2) {
|
|
$updated++;
|
|
}
|
|
} catch (PDOException $e) {
|
|
$failed++;
|
|
}
|
|
}
|
|
|
|
$db->commit();
|
|
|
|
// Log the import
|
|
logAction($db, null, 'INSERT', null, [
|
|
'type' => 'bulk_import',
|
|
'inserted' => $inserted,
|
|
'updated' => $updated,
|
|
'failed' => $failed
|
|
]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'inserted' => $inserted,
|
|
'updated' => $updated,
|
|
'failed' => $failed
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$db->rollBack();
|
|
jsonResponse(['error' => 'Import failed: ' . $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import entries from a remote URL
|
|
*/
|
|
function handleImportUrl($db) {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['error' => 'Method not allowed'], 405);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
// Validate CSRF
|
|
if (!validateCSRFToken($input['csrf_token'] ?? '')) {
|
|
jsonResponse(['error' => 'Invalid CSRF token'], 403);
|
|
}
|
|
|
|
$url = trim($input['url'] ?? '');
|
|
|
|
if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) {
|
|
jsonResponse(['error' => 'Invalid URL'], 400);
|
|
}
|
|
|
|
// Fetch the CSV
|
|
$ch = curl_init();
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_URL => $url,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_TIMEOUT => 30,
|
|
CURLOPT_SSL_VERIFYPEER => true,
|
|
CURLOPT_USERAGENT => 'Geofeed-Manager/1.0'
|
|
]);
|
|
|
|
$csvData = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$error = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
if ($error || $httpCode !== 200) {
|
|
jsonResponse(['error' => 'Failed to fetch URL: ' . ($error ?: "HTTP $httpCode")], 400);
|
|
}
|
|
|
|
if (empty($csvData)) {
|
|
jsonResponse(['error' => 'Empty response from URL'], 400);
|
|
}
|
|
|
|
// Parse CSV
|
|
$lines = explode("\n", $csvData);
|
|
$entries = [];
|
|
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
if (empty($line) || strpos($line, '#') === 0) {
|
|
continue;
|
|
}
|
|
|
|
$parts = str_getcsv($line);
|
|
if (count($parts) < 1 || empty(trim($parts[0]))) {
|
|
continue;
|
|
}
|
|
|
|
$entries[] = [
|
|
'ip_prefix' => trim($parts[0] ?? ''),
|
|
'country_code' => strtoupper(trim($parts[1] ?? '')),
|
|
'region_code' => strtoupper(trim($parts[2] ?? '')),
|
|
'city' => trim($parts[3] ?? ''),
|
|
'postal_code' => trim($parts[4] ?? '')
|
|
];
|
|
}
|
|
|
|
if (empty($entries)) {
|
|
jsonResponse(['error' => 'No valid entries found in CSV'], 400);
|
|
}
|
|
|
|
// Import entries
|
|
$inserted = 0;
|
|
$updated = 0;
|
|
$failed = 0;
|
|
|
|
$stmt = $db->prepare("
|
|
INSERT INTO geofeed_entries (ip_prefix, country_code, region_code, city, postal_code)
|
|
VALUES (:ip_prefix, :country_code, :region_code, :city, :postal_code)
|
|
ON DUPLICATE KEY UPDATE
|
|
country_code = VALUES(country_code),
|
|
region_code = VALUES(region_code),
|
|
city = VALUES(city),
|
|
postal_code = VALUES(postal_code),
|
|
updated_at = CURRENT_TIMESTAMP
|
|
");
|
|
|
|
$db->beginTransaction();
|
|
|
|
try {
|
|
foreach ($entries as $entry) {
|
|
$ipPrefix = $entry['ip_prefix'];
|
|
|
|
if (!isValidIpPrefix($ipPrefix)) {
|
|
$failed++;
|
|
continue;
|
|
}
|
|
|
|
$countryCode = $entry['country_code'];
|
|
if (!empty($countryCode) && !isValidCountryCode($countryCode)) {
|
|
$countryCode = null;
|
|
}
|
|
|
|
$regionCode = $entry['region_code'];
|
|
if (!empty($regionCode) && !isValidRegionCode($regionCode)) {
|
|
$regionCode = null;
|
|
}
|
|
|
|
try {
|
|
$stmt->execute([
|
|
':ip_prefix' => $ipPrefix,
|
|
':country_code' => $countryCode ?: null,
|
|
':region_code' => $regionCode ?: null,
|
|
':city' => $entry['city'] ?: null,
|
|
':postal_code' => $entry['postal_code'] ?: null
|
|
]);
|
|
|
|
if ($stmt->rowCount() === 1) {
|
|
$inserted++;
|
|
} elseif ($stmt->rowCount() === 2) {
|
|
$updated++;
|
|
}
|
|
} catch (PDOException $e) {
|
|
$failed++;
|
|
}
|
|
}
|
|
|
|
$db->commit();
|
|
|
|
// Log the import
|
|
logAction($db, null, 'INSERT', null, [
|
|
'type' => 'url_import',
|
|
'url' => $url,
|
|
'inserted' => $inserted,
|
|
'updated' => $updated,
|
|
'failed' => $failed
|
|
]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'inserted' => $inserted,
|
|
'updated' => $updated,
|
|
'failed' => $failed
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$db->rollBack();
|
|
jsonResponse(['error' => 'Import failed: ' . $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all entries
|
|
*/
|
|
function handleClearAll($db) {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
jsonResponse(['error' => 'Method not allowed'], 405);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
// Validate CSRF
|
|
if (!validateCSRFToken($input['csrf_token'] ?? '')) {
|
|
jsonResponse(['error' => 'Invalid CSRF token'], 403);
|
|
}
|
|
|
|
try {
|
|
// Get count before deletion
|
|
$countStmt = $db->query("SELECT COUNT(*) as count FROM geofeed_entries");
|
|
$count = $countStmt->fetch()['count'];
|
|
|
|
// Delete all entries
|
|
$db->exec("DELETE FROM geofeed_entries");
|
|
|
|
// Reset auto increment
|
|
$db->exec("ALTER TABLE geofeed_entries AUTO_INCREMENT = 1");
|
|
|
|
// Log the action
|
|
logAction($db, null, 'DELETE', ['count' => $count], ['type' => 'clear_all']);
|
|
|
|
jsonResponse(['success' => true, 'deleted' => $count]);
|
|
|
|
} catch (Exception $e) {
|
|
jsonResponse(['error' => 'Failed to clear entries: ' . $e->getMessage()], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log action to audit table
|
|
*/
|
|
function logAction($db, $entryId, $action, $oldValues, $newValues) {
|
|
$stmt = $db->prepare("
|
|
INSERT INTO geofeed_audit_log (entry_id, action, old_values, new_values, changed_by)
|
|
VALUES (:entry_id, :action, :old_values, :new_values, :changed_by)
|
|
");
|
|
|
|
$stmt->execute([
|
|
':entry_id' => $entryId,
|
|
':action' => $action,
|
|
':old_values' => $oldValues ? json_encode($oldValues) : null,
|
|
':new_values' => $newValues ? json_encode($newValues) : null,
|
|
':changed_by' => $_SERVER['REMOTE_ADDR'] ?? 'system'
|
|
]);
|
|
}
|