142 lines
4.1 KiB
PHP
142 lines
4.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('Cache-Control: no-store');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
http_response_code(204);
|
|
exit;
|
|
}
|
|
|
|
$secret = load_secret();
|
|
$requestSecret = $_SERVER['HTTP_X_ROUTES_SECRET'] ?? '';
|
|
|
|
if ($secret === '' || !hash_equals($secret, $requestSecret)) {
|
|
respond(401, ['error' => 'Unauthorized']);
|
|
}
|
|
|
|
$dataDir = __DIR__ . '/data';
|
|
$dataFile = $dataDir . '/routes.json';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|
respond(200, ['routes' => read_routes($dataFile)]);
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'PUT' || $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$payload = json_decode((string) file_get_contents('php://input'), true);
|
|
|
|
if (!is_array($payload) || !isset($payload['routes']) || !is_array($payload['routes'])) {
|
|
respond(400, ['error' => 'Invalid payload']);
|
|
}
|
|
|
|
$routes = array_map('sanitize_route', $payload['routes']);
|
|
write_routes($dataDir, $dataFile, $routes);
|
|
respond(200, ['ok' => true, 'routes' => $routes]);
|
|
}
|
|
|
|
respond(405, ['error' => 'Method not allowed']);
|
|
|
|
function load_secret(): string
|
|
{
|
|
$configFile = __DIR__ . '/config.js';
|
|
if (!is_file($configFile)) {
|
|
return '';
|
|
}
|
|
|
|
$config = (string) file_get_contents($configFile);
|
|
if (preg_match('/ROUTES_SECRET\s*=\s*(["\'])(.*?)\1\s*;?/', $config, $matches)) {
|
|
return stripcslashes($matches[2]);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
function read_routes(string $dataFile): array
|
|
{
|
|
if (!is_file($dataFile)) {
|
|
return [];
|
|
}
|
|
|
|
$handle = fopen($dataFile, 'rb');
|
|
if ($handle === false) {
|
|
respond(500, ['error' => 'Could not open routes file']);
|
|
}
|
|
|
|
flock($handle, LOCK_SH);
|
|
$contents = stream_get_contents($handle) ?: '[]';
|
|
flock($handle, LOCK_UN);
|
|
fclose($handle);
|
|
|
|
$routes = json_decode($contents, true);
|
|
return is_array($routes) ? $routes : [];
|
|
}
|
|
|
|
function write_routes(string $dataDir, string $dataFile, array $routes): void
|
|
{
|
|
if (!is_dir($dataDir) && !mkdir($dataDir, 0775, true) && !is_dir($dataDir)) {
|
|
respond(500, ['error' => 'Could not create data directory']);
|
|
}
|
|
|
|
$tmpFile = $dataFile . '.tmp';
|
|
$json = json_encode($routes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
if ($json === false) {
|
|
respond(500, ['error' => 'Could not encode routes']);
|
|
}
|
|
|
|
$handle = fopen($tmpFile, 'wb');
|
|
if ($handle === false) {
|
|
respond(500, ['error' => 'Could not write routes file']);
|
|
}
|
|
|
|
flock($handle, LOCK_EX);
|
|
fwrite($handle, $json);
|
|
fflush($handle);
|
|
flock($handle, LOCK_UN);
|
|
fclose($handle);
|
|
|
|
if (!rename($tmpFile, $dataFile)) {
|
|
@unlink($tmpFile);
|
|
respond(500, ['error' => 'Could not save routes file']);
|
|
}
|
|
}
|
|
|
|
function sanitize_route(array $route): array
|
|
{
|
|
$clean = [
|
|
'id' => trim((string) ($route['id'] ?? '')),
|
|
'name' => trim((string) ($route['name'] ?? '')),
|
|
'link' => trim((string) ($route['link'] ?? '')),
|
|
'date' => trim((string) ($route['date'] ?? '')),
|
|
'archived' => (bool) ($route['archived'] ?? false),
|
|
'picture' => (string) ($route['picture'] ?? ''),
|
|
'description' => trim((string) ($route['description'] ?? '')),
|
|
'updatedAt' => trim((string) ($route['updatedAt'] ?? '')),
|
|
];
|
|
|
|
if ($clean['id'] === '' || $clean['name'] === '' || $clean['link'] === '') {
|
|
respond(400, ['error' => 'Route id, name, and link are required']);
|
|
}
|
|
|
|
if (!filter_var($clean['link'], FILTER_VALIDATE_URL)) {
|
|
respond(400, ['error' => 'Invalid route link']);
|
|
}
|
|
|
|
if ($clean['date'] !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $clean['date'])) {
|
|
respond(400, ['error' => 'Invalid route date']);
|
|
}
|
|
|
|
if ($clean['picture'] !== '' && !str_starts_with($clean['picture'], 'data:image/') && !filter_var($clean['picture'], FILTER_VALIDATE_URL)) {
|
|
respond(400, ['error' => 'Invalid route picture']);
|
|
}
|
|
|
|
return $clean;
|
|
}
|
|
|
|
function respond(int $status, array $payload): never
|
|
{
|
|
http_response_code($status);
|
|
echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|