add styling

This commit is contained in:
Purple
2026-01-16 20:13:07 +00:00
parent 178c2997bd
commit 2385218831
5 changed files with 1170 additions and 170 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

10
.dockerignore Normal file
View 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
View 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

View File

@@ -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
*/

File diff suppressed because it is too large Load Diff