Files
shopPRO/autoload/api/ApiRouter.php
Jacek Pyziak 52119a0724 feat: database-backed cron job queue replacing JSON file system
Replace file-based JSON cron queue with DB-backed job queue (pp_cron_jobs,
pp_cron_schedules). New Domain\CronJob module: CronJobType (constants),
CronJobRepository (CRUD, atomic fetch, retry/backoff), CronJobProcessor
(orchestration with handler registration). Priority ordering guarantees
apilo_send_order (40) runs before sync tasks (50). Includes cron.php auth
protection, race condition fix in fetchNext, API response validation,
and DI wiring across all entry points. 41 new tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:29:11 +01:00

153 lines
5.1 KiB
PHP

<?php
namespace api;
use Domain\Settings\SettingsRepository;
class ApiRouter
{
private $db;
private $settingsRepo;
public function __construct($db, SettingsRepository $settingsRepo)
{
$this->db = $db;
$this->settingsRepo = $settingsRepo;
}
public function handle(): void
{
if (!headers_sent()) {
header('Content-Type: application/json; charset=utf-8');
}
try {
if (!$this->authenticate()) {
self::sendError('UNAUTHORIZED', 'Invalid or missing API key', 401);
return;
}
$endpoint = trim((string)($_GET['endpoint'] ?? ''));
$action = trim((string)($_GET['action'] ?? ''));
if ($endpoint === '' || $action === '') {
self::sendError('BAD_REQUEST', 'Missing endpoint or action parameter', 400);
return;
}
$controller = $this->resolveController($endpoint);
if ($controller === null) {
self::sendError('NOT_FOUND', 'Unknown endpoint: ' . $endpoint, 404);
return;
}
if (!method_exists($controller, $action)) {
self::sendError('NOT_FOUND', 'Unknown action: ' . $action, 404);
return;
}
$controller->$action();
} catch (\Throwable $e) {
self::sendError('INTERNAL_ERROR', 'Internal server error', 500);
}
}
private function authenticate(): bool
{
$headerKey = isset($_SERVER['HTTP_X_API_KEY']) ? $_SERVER['HTTP_X_API_KEY'] : '';
if ($headerKey === '') {
return false;
}
$storedKey = $this->settingsRepo->getSingleValue('api_key');
if ($storedKey === '') {
return false;
}
return hash_equals($storedKey, $headerKey);
}
private function resolveController(string $endpoint)
{
$factories = $this->getControllerFactories();
if (!isset($factories[$endpoint])) {
return null;
}
return $factories[$endpoint]();
}
private function getControllerFactories(): array
{
$db = $this->db;
return [
'orders' => function () use ($db) {
$orderRepo = new \Domain\Order\OrderRepository($db);
$settingsRepo = new \Domain\Settings\SettingsRepository($db);
$productRepo = new \Domain\Product\ProductRepository($db);
$transportRepo = new \Domain\Transport\TransportRepository($db);
$cronJobRepo = new \Domain\CronJob\CronJobRepository($db);
$service = new \Domain\Order\OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo, $cronJobRepo);
return new Controllers\OrdersApiController($service, $orderRepo);
},
'products' => function () use ($db) {
$productRepo = new \Domain\Product\ProductRepository($db);
$attrRepo = new \Domain\Attribute\AttributeRepository($db);
return new Controllers\ProductsApiController($productRepo, $attrRepo);
},
'dictionaries' => function () use ($db) {
$statusRepo = new \Domain\ShopStatus\ShopStatusRepository($db);
$transportRepo = new \Domain\Transport\TransportRepository($db);
$paymentRepo = new \Domain\PaymentMethod\PaymentMethodRepository($db);
$attrRepo = new \Domain\Attribute\AttributeRepository($db);
$producerRepo = new \Domain\Producer\ProducerRepository($db);
return new Controllers\DictionariesApiController($statusRepo, $transportRepo, $paymentRepo, $attrRepo, $producerRepo);
},
];
}
// =========================================================================
// Static response helpers
// =========================================================================
public static function sendSuccess($data): void
{
http_response_code(200);
echo json_encode(['status' => 'ok', 'data' => $data], JSON_UNESCAPED_UNICODE);
}
public static function sendError(string $code, string $message, int $httpCode = 400): void
{
http_response_code($httpCode);
echo json_encode([
'status' => 'error',
'code' => $code,
'message' => $message,
], JSON_UNESCAPED_UNICODE);
}
public static function getJsonBody(): ?array
{
$raw = file_get_contents('php://input');
if ($raw === '' || $raw === false) {
return null;
}
$data = json_decode($raw, true);
return is_array($data) ? $data : null;
}
public static function requireMethod(string $method): bool
{
$requestMethod = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
if ($requestMethod !== strtoupper($method)) {
self::sendError('METHOD_NOT_ALLOWED', 'Method ' . $requestMethod . ' not allowed, expected ' . strtoupper($method), 405);
return false;
}
return true;
}
}