TODO: Replace stubs

This commit is contained in:
2025-04-25 19:42:16 -04:00
commit 9bdecb1455
16 changed files with 2996 additions and 0 deletions

44
src/Facades/Config.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App\Facades;
use Illuminate\Support\Facades\Facade;
/**
* Class Config
*
* This class serves as a facade for the configuration settings of the application.
* It extends the Facade class from the Illuminate\Support\Facades namespace.
*
* @method static string|int|bool|float get(string $key, string $castTo = 'string') Retrieve the configuration value for the given key.
*
* @package Siteworx\App\Facades
*/
class Config extends Facade
{
public const string HTTP_PORT = 'HTTP_PORT';
public const string LOG_LEVEL = 'LOG_LEVEL';
public const string DB_DRIVER = 'DB_DRIVER';
public const string DB_HOST = 'DB_HOST';
public const string DB_DATABASE = 'DB_DATABASE';
public const string DB_USER = 'DB_USER';
public const string DB_PASSWORD = 'DB_PASSWORD';
public const string DEV_MODE = 'DEV_MODE';
public static function getFacadeRoot(): \Siteworxpro\App\Helpers\Config
{
return new \Siteworxpro\App\Helpers\Config();
}
/**
* Get the registered name of the component.
*
* @return string The name of the component.
*/
protected static function getFacadeAccessor(): string
{
return \Siteworxpro\App\Helpers\Config::class;
}
}

42
src/Facades/Logger.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App\Facades;
use Illuminate\Support\Facades\Facade;
use RoadRunner\Logger\Logger as RRLogger;
use Spiral\Goridge\RPC\RPC;
/**
* Class Logger
*
* This class serves as a facade for the Monolog logger.
* It extends the Facade class from the Illuminate\Support\Facades namespace.
*
* @method static info(string $message, array $context = []) Log an informational message.
* @method static error(string $message, array $context = []) Log an error message.
* @method static warning(string $message, array $context = []) Log a warning message.
*
* @package Siteworxpro\App\Facades
*/
class Logger extends Facade
{
public static function getFacadeRoot(): RRLogger
{
$rpc = RPC::create('tcp://127.0.0.1:6001');
return new RRLogger($rpc);
}
/**
* Get the registered name of the component.
*
* @return string The name of the component.
*/
protected static function getFacadeAccessor(): string
{
return RRLogger::class;
}
}

32
src/Helpers/Config.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App\Helpers;
use Psr\Log\LogLevel;
use Siteworxpro\App\Facades\Config as FacadeConfig;
class Config
{
private const array DEFAULTS = [
FacadeConfig::DB_DRIVER => 'pgsql',
FacadeConfig::DB_HOST => 'localhost',
FacadeConfig::DB_DATABASE => 'siteworx',
FacadeConfig::DB_USER => 'siteworx',
FacadeConfig::DB_PASSWORD => 'password',
FacadeConfig::LOG_LEVEL => LogLevel::DEBUG,
FacadeConfig::HTTP_PORT => '9501',
FacadeConfig::DEV_MODE => true,
];
/**
* @param string $key
* @param string $castTo
* @return string|int|bool|float
*/
public function get(string $key, string $castTo = 'string'): string|int|bool|float
{
return Env::get($key, self::DEFAULTS[$key] ?? null, $castTo);
}
}

26
src/Helpers/Env.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App\Helpers;
abstract class Env
{
/**
* @param string $key
* @param null $default
* @param string $castTo
* @return float|bool|int|string
*/
public static function get(string $key, $default = null, string $castTo = 'string'): float | bool | int | string
{
$env = getenv($key) !== false ? getenv($key) : $default;
return match ($castTo) {
'bool', 'boolean' => (bool) $env,
'int', 'integer' => (int) $env,
'float' => (float) $env,
default => (string) $env,
};
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App\Http;
use Nyholm\Psr7\Response;
/**
* Class JsonResponseFactory
*
* A factory class for creating JSON responses.
*/
class JsonResponseFactory
{
/**
* Create a JSON response with the given data and status code.
*
* @param mixed $data The data to include in the response.
* @param int $statusCode The HTTP status code for the response.
* @return Response The JSON response.
* @throws \JsonException
*/
public static function createJsonResponse(array $data, int $statusCode = 200): Response
{
return new Response(
status: $statusCode,
headers: [
'Content-Type' => 'application/json',
],
body: json_encode($data, JSON_THROW_ON_ERROR)
);
}
}

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App\Http\Middleware;
use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Siteworxpro\App\Facades\Config;
/**
* Class CorsMiddleware
*
* Middleware to handle CORS (Cross-Origin Resource Sharing) requests.
* It checks the origin of the request and sets appropriate CORS headers
* in the response.
*/
class CorsMiddleware implements MiddlewareInterface
{
/**
* Process the incoming request and add CORS headers to the response.
*
* @param ServerRequestInterface $request The incoming request.
* @param RequestHandlerInterface $handler The request handler.
* @return ResponseInterface The response with CORS headers.
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$origin = $request->getHeaderLine('Origin');
$allowedOrigins = array_map(
'trim', explode(
',',
Config::get('CORS_ALLOWED_ORIGINS', 'https://example.com,https://another.com')
));
$allowOrigin = in_array($origin, $allowedOrigins, true)
? $origin
: 'null';
if ($request->getMethod() === 'OPTIONS') {
$response = new Response(204);
} else {
$response = $handler->handle($request);
}
$response = $response
->withHeader('Access-Control-Allow-Origin', $allowOrigin)
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
->withHeader(
'Access-Control-Allow-Headers',
$request->getHeaderLine('Access-Control-Request-Headers')
?: 'Content-Type, Authorization'
);
if (Config::get('CORS_ALLOW_CREDENTIALS', 'bool')) {
$response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
}
$maxAge = Config::get('CORS_MAX_AGE') ?: '86400';
return $response->withHeader('Access-Control-Max-Age', $maxAge);
}
}

12
src/Models/Model.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App\Models;
use Illuminate\Database\Eloquent\Model as ORM;
abstract class Model extends ORM
{
}

170
src/Server.php Normal file
View File

@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
namespace Siteworxpro\App;
use League\Route\Http\Exception\MethodNotAllowedException;
use League\Route\Http\Exception\NotFoundException;
use League\Route\Router;
use Nyholm\Psr7\Factory\Psr17Factory;
use Siteworxpro\App\Facades\Config;
use Siteworxpro\App\Facades\Logger;
use Siteworxpro\App\Http\JsonResponseFactory;
use Siteworxpro\App\Http\Middleware\CorsMiddleware;
use Spiral\RoadRunner\Http\PSR7Worker;
use Spiral\RoadRunner\Worker;
/**
* Abstract class Server
*
* This abstract class serves as a base for creating server instances.
* It initializes the PSR-7 worker and router, and provides an abstract method
* for registering routes. It also includes a method to start the server and handle
* incoming requests.
*/
class Server
{
/**
* @var Router The router instance for handling routes.
*/
protected Router $router;
/**
* @var PSR7Worker The PSR-7 worker instance for handling HTTP requests.
*/
protected PSR7Worker $worker;
/**
* Server constructor.
*
* Initializes the server by booting the PSR-7 worker and router.
*/
public function __construct()
{
$this->boot();
}
/**
* Bootstraps the server by initializing the PSR-7 worker and router.
*
* This method sets up the PSR-7 worker and router instances, and registers
* the routes for the server. It should be called in the constructor of
* subclasses to ensure proper initialization.
*
* @return void
*/
private function boot(): void
{
$this->worker = new PSR7Worker(
Worker::create(),
new Psr17Factory(),
new Psr17Factory(),
new Psr17Factory()
);
$this->router = new Router();
$this->registerRoutes();
$this->bootModelCapsule();
}
/**
* Bootstraps the model capsule for database connections.
*
* This method sets up the database connection using the Eloquent ORM.
* It retrieves the database configuration from the Config facade and
* initializes the Eloquent capsule manager.
*
* @return void
*/
public function bootModelCapsule(): void
{
$capsule = new \Illuminate\Database\Capsule\Manager();
$capsule->addConnection([
'driver' => Config::get(Config::DB_DRIVER),
'host' => Config::get(Config::DB_HOST),
'database' => Config::get(Config::DB_DATABASE),
'username' => Config::get(Config::DB_USER),
'password' => Config::get(Config::DB_PASSWORD),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
]);
$capsule->setAsGlobal();
$capsule->bootEloquent();
}
/**
* Registers the routes for the server.
*
* This method is responsible for defining the routes that the server will handle.
* It should be implemented in subclasses to provide specific route definitions.
*
* @return void
*/
protected function registerRoutes(): void
{
$this->router->get('/', function () {
return JsonResponseFactory::createJsonResponse(['status_code' => 200, 'message' => 'Server is running']);
});
$this->router->middleware(new CorsMiddleware());
}
/**
* Starts the server and handles incoming requests.
*
* This method enters an infinite loop to continuously handle incoming HTTP requests.
* It decodes the request body, routes the request, and sends the response. It also handles
* exceptions and ensures proper cleanup after each request.
*
* @throws \JsonException If there is an error decoding the JSON request body.
*/
public function startServer(): void
{
Logger::info(sprintf('Server started: %s', microtime(true)));
Logger::info(sprintf('Server PID: %s', getmypid()));
Logger::info(sprintf('Server Listening on: %s:%s', Config::get('HTTP_HOST'), Config::get('HTTP_PORT')));
while (true) {
try {
$request = $this->worker->waitRequest();
if ($request === null) {
break;
}
$request = $request->withParsedBody(json_decode($request->getBody()->getContents(), true));
$response = $this->router->handle($request);
$this->worker->respond($response);
} catch (MethodNotAllowedException|NotFoundException) {
$this->worker->respond(
JsonResponseFactory::createJsonResponse(
['status_code' => 404, 'reason_phrase' => 'Not Found'],
404
)
);
} catch (\Throwable $e) {
Logger::error($e->getMessage());
Logger::error($e->getTraceAsString());
$json = ['status_code' => 500, 'reason_phrase' => 'Server Error'];
if (Config::get("DEV_MODE", 'bool')) {
$json = [
'status_code' => 500,
'reason_phrase' => 'Server Error',
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
];
}
$this->worker->respond(JsonResponseFactory::createJsonResponse($json, 500));
}
}
}
}