diff --git a/api/.htaccess b/api/.htaccess
new file mode 100644
index 0000000..ab8df66
--- /dev/null
+++ b/api/.htaccess
@@ -0,0 +1,28 @@
+RewriteEngine On
+
+# --------------------------------------------------------------
+# 1) Direkter Aufruf von PHP-Dateien verhindern und auf index.php routen
+# Beispiel:
+# /v1/browser.quick.test.php -> /index.php (mit REQUEST_URI erhalten)
+# --------------------------------------------------------------
+RewriteCond %{REQUEST_URI} !/index\.php$
+RewriteRule ^(.+)\.php$ /index.php [QSA,L]
+
+# --------------------------------------------------------------
+# 2) Echte Dateien (JSON, JS, CSS, Bilder etc.) normal ausliefern
+# --------------------------------------------------------------
+RewriteCond %{REQUEST_FILENAME} -f
+RewriteRule ^ - [L]
+
+# --------------------------------------------------------------
+# 3) Echte Verzeichnisse normal ausliefern
+# --------------------------------------------------------------
+RewriteCond %{REQUEST_FILENAME} -d
+RewriteRule ^ - [L]
+
+# --------------------------------------------------------------
+# 4) Alles andere durch index.php routen
+# Beispiel:
+# /v1/quickcheck -> index.php
+# --------------------------------------------------------------
+RewriteRule ^ /index.php [QSA,L]
diff --git a/api/index.php b/api/index.php
index 41a41cf..c24e1be 100644
--- a/api/index.php
+++ b/api/index.php
@@ -1,49 +1,58 @@
false,
- 'error' => 'Unknown endpoint',
- 'path' => $path,
- ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
- break;
+// Root-Info (optional)
+if ($path === '/') {
+ echo json_encode([
+ 'ok' => true,
+ 'service' => 'usbcheck-api',
+ 'version' => 1,
+ 'endpoints' => [
+ '/v1/quickcheck',
+ '/v1/browser.quick.test',
+ '/internal/* (geschützt)',
+ ],
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ exit;
}
+// Routing nach Bereich
+if (str_starts_with($path, '/v1/')) {
+ require __DIR__ . '/router.v1.php';
+ exit;
+}
+if (str_starts_with($path, '/internal/')) {
+ require __DIR__ . '/router.internal.php';
+ exit;
+}
+// Fallback: unbekannter Bereich
+http_response_code(404);
+echo json_encode([
+ 'ok' => false,
+ 'error' => 'Unknown API area',
+ 'path' => $path,
+], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
diff --git a/api/router.internal.php b/api/router.internal.php
new file mode 100644
index 0000000..589a701
--- /dev/null
+++ b/api/router.internal.php
@@ -0,0 +1,100 @@
+ false,
+ 'error' => 'Authentication required',
+ ]);
+ exit;
+}
+
+// Pfad erneut bestimmen
+$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
+$path = rtrim($uri, '/');
+
+// DB einbinden (für interne Tools brauchen wir oft DB)
+require $_SERVER['DOCUMENT_ROOT'] . '/../config/db.php';
+
+// interne Routen
+switch ($path) {
+ // Beispiel: Aggregierte Stats
+ case '/internal/stats.overview':
+ internal_stats_overview($pdo);
+ break;
+
+ // Beispiel: Wartung / Cleanup
+ case '/internal/maintenance.cleanup-tests':
+ internal_cleanup_tests($pdo);
+ break;
+
+ default:
+ http_response_code(404);
+ echo json_encode([
+ 'ok' => false,
+ 'error' => 'Unknown internal endpoint',
+ 'path' => $path,
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ break;
+}
+
+/**
+ * Beispiel: einfache Übersicht für Admin-Dashboard
+ */
+function internal_stats_overview(PDO $pdo): void
+{
+ // alles nur Beispiel – du kannst die Queries anpassen
+ $totalQuicktests = (int)$pdo->query("SELECT COUNT(*) FROM web_quicktests")->fetchColumn();
+
+ $lastTestsStmt = $pdo->query("
+ SELECT id, created_at, ip_address, measured_capacity_bytes
+ FROM web_quicktests
+ ORDER BY created_at DESC
+ LIMIT 10
+ ");
+
+ $lastTests = $lastTestsStmt ? $lastTestsStmt->fetchAll(PDO::FETCH_ASSOC) : [];
+
+ echo json_encode([
+ 'ok' => true,
+ 'stats' => [
+ 'total_quicktests' => $totalQuicktests,
+ 'last_quicktests' => $lastTests,
+ ],
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+}
+
+/**
+ * Beispiel: alte Tests aufräumen (z.B. älter als 90 Tage)
+ */
+function internal_cleanup_tests(PDO $pdo): void
+{
+ // je nach Schema musst du Feldnamen anpassen – hier: created_at
+ $stmt = $pdo->prepare("
+ DELETE FROM web_quicktests
+ WHERE created_at < (NOW() - INTERVAL 90 DAY)
+ ");
+
+ $stmt->execute();
+ $deleted = $stmt->rowCount();
+
+ echo json_encode([
+ 'ok' => true,
+ 'deleted' => $deleted,
+ 'note' => 'Tests älter als 90 Tage wurden entfernt (Beispiel-Implementierung).',
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+}
diff --git a/api/router.v1.php b/api/router.v1.php
new file mode 100644
index 0000000..d67e4ee
--- /dev/null
+++ b/api/router.v1.php
@@ -0,0 +1,43 @@
+ false, 'error' => 'Handler quickcheck_handle_request not found']);
+ exit;
+ }
+
+ $result = quickcheck_handle_request();
+ echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ break;
+
+ case '/v1/browser.quick.test':
+ require __DIR__ . '/v1/result/browser.quick.test.php';
+ if (!function_exists('browser_quick_test_handle_request')) {
+ http_response_code(500);
+ echo json_encode(['ok' => false, 'error' => 'Handler browser_quick_test_handle_request not found']);
+ exit;
+ }
+
+ browser_quick_test_handle_request();
+ break;
+
+ default:
+ http_response_code(404);
+ echo json_encode([
+ 'ok' => false,
+ 'error' => 'Unknown v1 endpoint',
+ 'path' => $path,
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ break;
+}
diff --git a/api/target/.htaccess b/api/target/.htaccess
deleted file mode 100644
index 17eeed7..0000000
--- a/api/target/.htaccess
+++ /dev/null
@@ -1,11 +0,0 @@
-# api/target/.htaccess
-
- Require all denied
-
-
-
- Order allow,deny
- Deny from all
-
-
-
diff --git a/api/target/browser.quick.test.php b/api/target/browser.quick.test.php
deleted file mode 100644
index 3ea19d4..0000000
--- a/api/target/browser.quick.test.php
+++ /dev/null
@@ -1,155 +0,0 @@
- false,
- 'error' => 'Invalid JSON payload',
- ];
- }
-
- // 2. Session / User
- // (falls index.php evtl. schon session_start() macht, ist das idempotent)
- if (session_status() !== PHP_SESSION_ACTIVE) {
- session_start();
- }
-
- $userId = $_SESSION['user_id'] ?? null;
- $isLoggedIn = $userId ? 1 : 0;
- $sessionId = session_id() ?: null;
-
- $ipAddress = $_SERVER['REMOTE_ADDR'] ?? null;
- $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
-
- // 3. DB-Verbindung
- // Dokumentroot der API-Subdomain zeigt auf /api,
- // config liegt ein Level darüber: /config/db.php
- require $_SERVER['DOCUMENT_ROOT'] . '/../config/db.php'; // $pdo
-
- if ($pdo instanceof PDO) {
- $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- }
-
- // 4. Werte aus dem Report aggregieren (minimal)
- $measuredBytes = 0;
-
- if (!empty($data['quick']) && is_array($data['quick'])) {
- $measuredBytes += (int)($data['quick']['size_bytes'] ?? 0);
- }
- if (!empty($data['benchmark']) && is_array($data['benchmark'])) {
- $measuredBytes += (int)($data['benchmark']['size_bytes'] ?? 0);
- }
- if (!empty($data['writeverify']) && is_array($data['writeverify'])) {
- $measuredBytes += (int)($data['writeverify']['total_bytes'] ?? 0);
- }
-
- // Browser/OS & Stick-Infos – erstmal noch leer, später aus meta/parsing füllen
- $browserName = null;
- $browserVersion = null;
- $osName = null;
- $osVersion = null;
-
- $volumeLabel = null;
- $manufacturer = null;
- $modelName = null;
- $usbType = null;
- $filesystem = null;
-
- $advCapacityBytes = null;
- $capacityStatus = 'unknown';
-
- // Kompletten Report als JSON-String speichern
- $testReportJson = $raw;
-
- try {
- $sql = "
- INSERT INTO web_quicktests (
- user_id,
- is_logged_in,
- usb_device_id,
- browser_name,
- browser_version,
- os_name,
- os_version,
- volume_label,
- manufacturer,
- model_name,
- usb_type,
- advertised_capacity_bytes,
- measured_capacity_bytes,
- capacity_status,
- filesystem,
- test_report_json,
- ip_address,
- session_id
- ) VALUES (
- :user_id,
- :is_logged_in,
- :usb_device_id,
- :browser_name,
- :browser_version,
- :os_name,
- :os_version,
- :volume_label,
- :manufacturer,
- :model_name,
- :usb_type,
- :adv_capacity,
- :measured_capacity,
- :capacity_status,
- :filesystem,
- :test_report_json,
- :ip_address,
- :session_id
- )
- ";
-
- $stmt = $pdo->prepare($sql);
-
- $ok = $stmt->execute([
- 'user_id' => $userId,
- 'is_logged_in' => $isLoggedIn,
- 'usb_device_id' => null,
- 'browser_name' => $browserName,
- 'browser_version' => $browserVersion,
- 'os_name' => $osName,
- 'os_version' => $osVersion,
- 'volume_label' => $volumeLabel,
- 'manufacturer' => $manufacturer,
- 'model_name' => $modelName,
- 'usb_type' => $usbType,
- 'adv_capacity' => $advCapacityBytes,
- 'measured_capacity' => $measuredBytes ?: null,
- 'capacity_status' => $capacityStatus,
- 'filesystem' => $filesystem,
- 'test_report_json' => $testReportJson,
- 'ip_address' => $ipAddress,
- 'session_id' => $sessionId,
- ]);
-
- if (!$ok) {
- $info = $stmt->errorInfo();
- throw new RuntimeException($info[2] ?? 'Unknown DB error during insert');
- }
-
- return [
- 'ok' => true,
- 'id' => (int)$pdo->lastInsertId(),
- ];
- } catch (Throwable $e) {
- http_response_code(500);
- return [
- 'ok' => false,
- 'error' => 'DB error: ' . $e->getMessage(),
- ];
- }
-}
diff --git a/api/result/browser-quick-test.php b/api/v1/result/browser.quick.test.php
similarity index 89%
rename from api/result/browser-quick-test.php
rename to api/v1/result/browser.quick.test.php
index 5a1e9d3..e7f399d 100644
--- a/api/result/browser-quick-test.php
+++ b/api/v1/result/browser.quick.test.php
@@ -1,12 +1,12 @@