Slim API deployment - composer install + full CRUD endpoints
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Repositories\UserRepository;
|
||||
|
||||
final class Auth
|
||||
{
|
||||
public static function user(): ?array
|
||||
{
|
||||
$userId = $_SESSION['user_id'] ?? null;
|
||||
if (!$userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (new UserRepository())->findById((int) $userId);
|
||||
}
|
||||
|
||||
public static function requireUser(): array
|
||||
{
|
||||
$user = self::user();
|
||||
if ($user === null) {
|
||||
Response::json(['message' => 'Unauthenticated'], 401);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public static function requireRole(array|string $roles): array
|
||||
{
|
||||
$user = self::requireUser();
|
||||
$roles = (array) $roles;
|
||||
|
||||
if (!in_array($user['role'], $roles, true)) {
|
||||
Response::json(['message' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use PDO;
|
||||
|
||||
final class Database
|
||||
{
|
||||
private static string $path;
|
||||
private static ?PDO $connection = null;
|
||||
|
||||
public static function configure(string $path): void
|
||||
{
|
||||
self::$path = $path;
|
||||
}
|
||||
|
||||
public static function connection(): PDO
|
||||
{
|
||||
if (self::$connection instanceof PDO) {
|
||||
return self::$connection;
|
||||
}
|
||||
|
||||
$directory = dirname(self::$path);
|
||||
if (!is_dir($directory)) {
|
||||
mkdir($directory, 0775, true);
|
||||
}
|
||||
|
||||
self::$connection = new PDO('sqlite:' . self::$path);
|
||||
self::$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
self::$connection->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
||||
self::$connection->exec('PRAGMA foreign_keys = ON');
|
||||
|
||||
return self::$connection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
final class Request
|
||||
{
|
||||
public function method(): string
|
||||
{
|
||||
return strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
|
||||
}
|
||||
|
||||
public function path(): string
|
||||
{
|
||||
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
$path = parse_url($uri, PHP_URL_PATH) ?: '/';
|
||||
|
||||
return rtrim($path, '/') ?: '/';
|
||||
}
|
||||
|
||||
public function query(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $_GET[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function input(): array
|
||||
{
|
||||
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
|
||||
|
||||
if (str_contains($contentType, 'application/json')) {
|
||||
$raw = file_get_contents('php://input');
|
||||
$decoded = json_decode($raw ?: '[]', true);
|
||||
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
return $_POST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
final class Response
|
||||
{
|
||||
public static function json(array $data, int $status = 200): never
|
||||
{
|
||||
http_response_code($status);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function noContent(): never
|
||||
{
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
final class Router
|
||||
{
|
||||
private array $routes = [];
|
||||
|
||||
public function __construct(private readonly Request $request)
|
||||
{
|
||||
}
|
||||
|
||||
public function get(string $path, callable $handler): void
|
||||
{
|
||||
$this->map('GET', $path, $handler);
|
||||
}
|
||||
|
||||
public function post(string $path, callable $handler): void
|
||||
{
|
||||
$this->map('POST', $path, $handler);
|
||||
}
|
||||
|
||||
public function patch(string $path, callable $handler): void
|
||||
{
|
||||
$this->map('PATCH', $path, $handler);
|
||||
}
|
||||
|
||||
public function delete(string $path, callable $handler): void
|
||||
{
|
||||
$this->map('DELETE', $path, $handler);
|
||||
}
|
||||
|
||||
public function dispatch(): void
|
||||
{
|
||||
$method = $this->request->method();
|
||||
$path = $this->request->path();
|
||||
|
||||
foreach ($this->routes[$method] ?? [] as $route) {
|
||||
$pattern = preg_replace('#\{([a-zA-Z_][a-zA-Z0-9_]*)\}#', '(?P<$1>[^/]+)', $route['path']);
|
||||
$pattern = '#^' . $pattern . '$#';
|
||||
|
||||
if (!preg_match($pattern, $path, $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$params = array_filter($matches, static fn (string|int $key): bool => is_string($key), ARRAY_FILTER_USE_KEY);
|
||||
$route['handler']($this->request, $params);
|
||||
return;
|
||||
}
|
||||
|
||||
Response::json(['message' => 'Route not found', 'path' => $path], 404);
|
||||
}
|
||||
|
||||
private function map(string $method, string $path, callable $handler): void
|
||||
{
|
||||
$this->routes[$method][] = [
|
||||
'path' => rtrim($path, '/') ?: '/',
|
||||
'handler' => $handler,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user