add styling
This commit is contained in:
10
.dockerignore
Normal file
10
.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
.env.example
|
||||
*.md
|
||||
LICENSE
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
import-geofeed.sh
|
||||
n8n/
|
||||
17
.env.example
Normal file
17
.env.example
Normal file
@@ -0,0 +1,17 @@
|
||||
# Geofeed Manager Environment Configuration
|
||||
# Copy this file to .env and update the values
|
||||
|
||||
# Database Configuration
|
||||
DB_ROOT_PASSWORD=change_me_root_password
|
||||
DB_NAME=geofeed_manager
|
||||
DB_USER=geofeed
|
||||
DB_PASSWORD=change_me_user_password
|
||||
|
||||
# Port Configuration
|
||||
DB_PORT=3306
|
||||
WEB_PORT=8080
|
||||
PMA_PORT=8081
|
||||
|
||||
# BunnyCDN Configuration (for n8n workflow)
|
||||
BUNNY_STORAGE_ZONE=your-storage-zone
|
||||
BUNNY_API_KEY=your-api-key
|
||||
301
webapp/api.php
301
webapp/api.php
@@ -54,6 +54,18 @@ try {
|
||||
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);
|
||||
}
|
||||
@@ -438,6 +450,295 @@ function handleSearch($db) {
|
||||
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
|
||||
*/
|
||||
|
||||
926
webapp/index.php
926
webapp/index.php
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user