Add file uploads and speed distribution chart
- File upload/download/delete per customer (max 20MB, stored in data/files/) - Files section shown in customer detail modal - Speed distribution chart replaces single "top speed" stat - Bar chart shows all speeds with count, top speed bolded - Customer delete also cleans up associated files - data/files/ added to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
120
api.php
120
api.php
@@ -159,6 +159,120 @@ switch ($action) {
|
||||
saveCustomers($customers);
|
||||
break;
|
||||
|
||||
case 'file_upload':
|
||||
requireAuth();
|
||||
if ($method !== 'POST') break;
|
||||
$customerId = $_POST['customer_id'] ?? '';
|
||||
if (!$customerId || !preg_match('/^[a-f0-9]+$/', $customerId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Virheellinen asiakas-ID']);
|
||||
break;
|
||||
}
|
||||
if (empty($_FILES['file'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Tiedosto puuttuu']);
|
||||
break;
|
||||
}
|
||||
$file = $_FILES['file'];
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Tiedoston lähetys epäonnistui']);
|
||||
break;
|
||||
}
|
||||
// Max 20MB
|
||||
if ($file['size'] > 20 * 1024 * 1024) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Tiedosto on liian suuri (max 20 MB)']);
|
||||
break;
|
||||
}
|
||||
$uploadDir = __DIR__ . '/data/files/' . $customerId;
|
||||
if (!file_exists($uploadDir)) mkdir($uploadDir, 0755, true);
|
||||
$safeName = preg_replace('/[^a-zA-Z0-9._-]/', '_', basename($file['name']));
|
||||
// Jos samanniminen tiedosto on jo olemassa, lisää aikaleima
|
||||
$dest = $uploadDir . '/' . $safeName;
|
||||
if (file_exists($dest)) {
|
||||
$ext = pathinfo($safeName, PATHINFO_EXTENSION);
|
||||
$base = pathinfo($safeName, PATHINFO_FILENAME);
|
||||
$safeName = $base . '_' . date('His') . ($ext ? '.' . $ext : '');
|
||||
$dest = $uploadDir . '/' . $safeName;
|
||||
}
|
||||
if (move_uploaded_file($file['tmp_name'], $dest)) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'filename' => $safeName,
|
||||
'size' => $file['size'],
|
||||
]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Tallennusvirhe']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'file_list':
|
||||
requireAuth();
|
||||
$customerId = $_GET['customer_id'] ?? '';
|
||||
if (!$customerId || !preg_match('/^[a-f0-9]+$/', $customerId)) {
|
||||
echo json_encode([]);
|
||||
break;
|
||||
}
|
||||
$dir = __DIR__ . '/data/files/' . $customerId;
|
||||
$files = [];
|
||||
if (is_dir($dir)) {
|
||||
foreach (scandir($dir) as $f) {
|
||||
if ($f === '.' || $f === '..') continue;
|
||||
$path = $dir . '/' . $f;
|
||||
$files[] = [
|
||||
'filename' => $f,
|
||||
'size' => filesize($path),
|
||||
'modified' => date('Y-m-d H:i', filemtime($path)),
|
||||
];
|
||||
}
|
||||
}
|
||||
usort($files, fn($a, $b) => strcmp($b['modified'], $a['modified']));
|
||||
echo json_encode($files);
|
||||
break;
|
||||
|
||||
case 'file_download':
|
||||
requireAuth();
|
||||
$customerId = $_GET['customer_id'] ?? '';
|
||||
$filename = $_GET['filename'] ?? '';
|
||||
if (!$customerId || !preg_match('/^[a-f0-9]+$/', $customerId) || !$filename) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Virheelliset parametrit']);
|
||||
break;
|
||||
}
|
||||
$safeName = basename($filename);
|
||||
$path = __DIR__ . '/data/files/' . $customerId . '/' . $safeName;
|
||||
if (!file_exists($path)) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Tiedostoa ei löydy']);
|
||||
break;
|
||||
}
|
||||
header('Content-Type: application/octet-stream');
|
||||
header('Content-Disposition: attachment; filename="' . $safeName . '"');
|
||||
header('Content-Length: ' . filesize($path));
|
||||
readfile($path);
|
||||
exit;
|
||||
|
||||
case 'file_delete':
|
||||
requireAuth();
|
||||
if ($method !== 'POST') break;
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$customerId = $input['customer_id'] ?? '';
|
||||
$filename = $input['filename'] ?? '';
|
||||
if (!$customerId || !preg_match('/^[a-f0-9]+$/', $customerId) || !$filename) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Virheelliset parametrit']);
|
||||
break;
|
||||
}
|
||||
$safeName = basename($filename);
|
||||
$path = __DIR__ . '/data/files/' . $customerId . '/' . $safeName;
|
||||
if (file_exists($path)) {
|
||||
unlink($path);
|
||||
}
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
|
||||
case 'customer_delete':
|
||||
requireAuth();
|
||||
if ($method !== 'POST') break;
|
||||
@@ -167,6 +281,12 @@ switch ($action) {
|
||||
$customers = loadCustomers();
|
||||
$customers = array_values(array_filter($customers, fn($c) => $c['id'] !== $id));
|
||||
saveCustomers($customers);
|
||||
// Poista asiakkaan tiedostot
|
||||
$filesDir = __DIR__ . '/data/files/' . $id;
|
||||
if (is_dir($filesDir)) {
|
||||
array_map('unlink', glob($filesDir . '/*'));
|
||||
rmdir($filesDir);
|
||||
}
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user