api.php
This commit is contained in:
parent
3f670ee09f
commit
ebb9c18177
1 changed files with 142 additions and 0 deletions
142
api.php
Normal file
142
api.php
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<?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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue