Compare commits
10 Commits
982d348a34
...
f788ba63ae
Author | SHA1 | Date | |
---|---|---|---|
f788ba63ae | |||
f5abbcb8c9 | |||
07b73e7f7f | |||
4a66af0f2b | |||
e58c442ccf | |||
7f05554f4e | |||
7b64e688be | |||
0791855c79 | |||
1de8da8691 | |||
5c72cdbfd5 |
@ -1,6 +1,7 @@
|
|||||||
|
APP_PORT=8000
|
||||||
DEPLOYMENT_ENV="development"
|
DEPLOYMENT_ENV="development"
|
||||||
DATABASE_HOST="localhost"
|
DB_HOST="localhost"
|
||||||
DATABASE_USER="slovocast"
|
DB_USER="slovocast"
|
||||||
DATABASE_PASSWORD="Password01"
|
DB_PASSWORD="Password01"
|
||||||
DATABASE_SCHEMA="slovocast"
|
DB_SCHEMA="slovocast"
|
||||||
DATABASE_PORT=3306
|
DB_PORT=3306
|
||||||
|
@ -15,7 +15,8 @@
|
|||||||
"dotenv-org/phpdotenv-vault": "^0.2.4",
|
"dotenv-org/phpdotenv-vault": "^0.2.4",
|
||||||
"react/react": "^1.4",
|
"react/react": "^1.4",
|
||||||
"robmorgan/phinx": "^0.16.1",
|
"robmorgan/phinx": "^0.16.1",
|
||||||
"react/mysql": "^0.7dev"
|
"react/mysql": "^0.7dev",
|
||||||
|
"react/async": "^4.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^11.1",
|
"phpunit/phpunit": "^11.1",
|
||||||
|
494
app/composer.lock
generated
494
app/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,34 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once '../vendor/autoload.php';
|
require_once realpath(__DIR__ . '/../vendor/autoload.php');
|
||||||
|
|
||||||
use Slovocast\Bootstrap;
|
use Slovocast\Bootstrap;
|
||||||
use React\Http\HttpServer;
|
use React\Http\HttpServer;
|
||||||
use React\Socket\SocketServer;
|
use React\Socket\SocketServer;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
|
||||||
$app = Bootstrap::init();
|
try {
|
||||||
|
$app = Bootstrap::init();
|
||||||
|
$http = new HttpServer(fn (Request $request) => $app->handle($request));
|
||||||
|
$address = "0.0.0.0";
|
||||||
|
$port = "8000";
|
||||||
|
$socket = new SocketServer($address . ":" . $port);
|
||||||
|
|
||||||
$http = new HttpServer(fn (Request $request) => $app->handle($request));
|
$http->on('error', function ($error) use ($socket) {
|
||||||
$address = "127.0.0.1:8000";
|
fprintf(STDERR, $error->getMessage() . "\n");
|
||||||
$socket = new SocketServer($address);
|
fprintf(STDERR, "Closing socket connection.\n");
|
||||||
$http->listen($socket);
|
$socket->close();
|
||||||
|
});
|
||||||
|
|
||||||
echo "Server running at $address" . PHP_EOL;
|
$http->on('exit', function () use ($socket) {
|
||||||
|
fprintf(STDOUT, "Closing socket connection.\n");
|
||||||
|
$socket->close();
|
||||||
|
});
|
||||||
|
|
||||||
|
$http->listen($socket);
|
||||||
|
fprintf(STDOUT, "Server running on %s\n", $address);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
fprintf(STDERR, "Error caught bootstrapping the application\n");
|
||||||
|
fprintf(STDERR, $e->getMessage() . "\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
@ -2,51 +2,46 @@
|
|||||||
|
|
||||||
namespace Slovocast;
|
namespace Slovocast;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Psr\Container\ContainerExceptionInterface;
|
|
||||||
use Psr\Container\NotFoundExceptionInterface;
|
|
||||||
use Slim\App;
|
|
||||||
use Slim\Factory\AppFactory;
|
|
||||||
|
|
||||||
use League\Config\Configuration;
|
|
||||||
|
|
||||||
use Psr\Container\ContainerInterface;
|
|
||||||
use Psr\Http\Message\ResponseFactoryInterface;
|
|
||||||
use DI\Container;
|
use DI\Container;
|
||||||
use DI\ContainerBuilder;
|
use DI\ContainerBuilder;
|
||||||
|
use Exception;
|
||||||
use Slim\Psr7\Factory\ResponseFactory;
|
use League\Config\Configuration;
|
||||||
|
|
||||||
use Slovocast\Configuration\SiteInformationSchema;
|
|
||||||
use Slovocast\Configuration\DatabaseConnectionSchema;
|
|
||||||
use Slovocast\Configuration\SessionSchema;
|
|
||||||
|
|
||||||
use Twig\Error\LoaderError;
|
|
||||||
use Slovocast\Domain\Repository\User\UserRepositoryInterface;
|
|
||||||
use Slovocast\Domain\Repository\User\UserRepository;
|
|
||||||
|
|
||||||
use Slovocast\Infrastructure\User\UserAuthorizationInterface;
|
|
||||||
use Slovocast\Infrastructure\User\BasicUserAuthorization;
|
|
||||||
|
|
||||||
use React\Mysql\MysqlClient;
|
|
||||||
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Monolog\Logger;
|
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Handler\StreamHandler;
|
||||||
use Monolog\Level;
|
use Monolog\Level;
|
||||||
|
use Monolog\Logger;
|
||||||
use Odan\Session\PhpSession;
|
use Odan\Session\PhpSession;
|
||||||
use Odan\Session\SessionInterface;
|
use Odan\Session\SessionInterface;
|
||||||
use Odan\Session\SessionManagerInterface;
|
use Odan\Session\SessionManagerInterface;
|
||||||
|
use Psr\Container\ContainerExceptionInterface;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Container\NotFoundExceptionInterface;
|
||||||
|
use Psr\Http\Message\ResponseFactoryInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use React\Mysql\MysqlClient;
|
||||||
|
use Slim\App;
|
||||||
|
use Slim\Factory\AppFactory;
|
||||||
|
use Slim\Psr7\Factory\ResponseFactory;
|
||||||
|
use Slovocast\Configuration\DatabaseConnectionSchema;
|
||||||
|
use Slovocast\Configuration\SessionSchema;
|
||||||
|
use Slovocast\Configuration\SiteInformationSchema;
|
||||||
|
use Slovocast\Domain\Repository\User\UserRepository;
|
||||||
|
use Slovocast\Domain\Repository\User\UserRepositoryInterface;
|
||||||
|
use Slovocast\Infrastructure\Api\Database\ConnectionPoolInterface;
|
||||||
|
use Slovocast\Infrastructure\Api\User\UserAuthorizationInterface;
|
||||||
|
use Slovocast\Infrastructure\Database\ConnectionPoolConfig;
|
||||||
|
use Slovocast\Infrastructure\Database\ConnectionPool;
|
||||||
|
use Slovocast\Infrastructure\User\BasicUserAuthorization;
|
||||||
|
use Twig\Error\LoaderError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines here are used globally
|
* Defines here are used globally
|
||||||
*/
|
*/
|
||||||
define('APP_ROOT_DIR', __DIR__ . '/..');
|
define('APP_ROOT_DIR', realpath(__DIR__ . '/..'));
|
||||||
define('APP_SRC_DIR', __DIR__);
|
define('APP_SRC_DIR', realpath(__DIR__));
|
||||||
define('APP_PUBLIC_DIR', __DIR__ . '/../public');
|
define('APP_PUBLIC_DIR', realpath(__DIR__ . '/../public'));
|
||||||
define('APP_TEMPLATES_DIR', __DIR__ . '/../templates');
|
define('APP_TEMPLATES_DIR', realpath(__DIR__ . '/../templates'));
|
||||||
define('APP_LOGS_DIR', __DIR__ . '/../var/logs');
|
define('APP_LOGS_DIR', realpath(__DIR__ . '/../var/logs'));
|
||||||
define('APP_TEMP_DIR', __DIR__ . '/../var/temp');
|
define('APP_TEMP_DIR', realpath(__DIR__ . '/../var/temp'));
|
||||||
|
|
||||||
class Bootstrap
|
class Bootstrap
|
||||||
{
|
{
|
||||||
@ -59,12 +54,14 @@ class Bootstrap
|
|||||||
{
|
{
|
||||||
$config = new Configuration();
|
$config = new Configuration();
|
||||||
$dotenv = \DotenvVault\DotenvVault::createImmutable(APP_ROOT_DIR);
|
$dotenv = \DotenvVault\DotenvVault::createImmutable(APP_ROOT_DIR);
|
||||||
|
$dotenv->safeLoad();
|
||||||
|
|
||||||
// set all configuration details
|
// set all configuration details
|
||||||
$config->addSchema('site', SiteInformationSchema::getSchema());
|
$config->addSchema('site', SiteInformationSchema::getSchema());
|
||||||
$config->addSchema('database', DatabaseConnectionSchema::getSchema());
|
$config->addSchema('database', DatabaseConnectionSchema::getSchema());
|
||||||
$config->addSchema('session', SessionSchema::getSchema());
|
$config->addSchema('session', SessionSchema::getSchema());
|
||||||
|
|
||||||
|
|
||||||
$config->merge([
|
$config->merge([
|
||||||
'site' => [
|
'site' => [
|
||||||
'name' => "Slovocast",
|
'name' => "Slovocast",
|
||||||
@ -77,10 +74,11 @@ class Bootstrap
|
|||||||
'name' => 'slovocast'
|
'name' => 'slovocast'
|
||||||
],
|
],
|
||||||
'database' => [
|
'database' => [
|
||||||
'host' => '127.0.0.1',
|
'host' => $_ENV['DB_HOST'],
|
||||||
'database' => 'slovocast',
|
'database' => $_ENV['DB_SCHEMA'],
|
||||||
'username' => 'slovocast',
|
'username' => $_ENV['DB_USER'],
|
||||||
'password' => 'Password01',
|
'password' => $_ENV['DB_PASSWORD'],
|
||||||
|
'port' => (int) $_ENV['DB_PORT'],
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -130,16 +128,17 @@ class Bootstrap
|
|||||||
/**
|
/**
|
||||||
* Database Connections
|
* Database Connections
|
||||||
*/
|
*/
|
||||||
MysqlClient::class => function (ContainerInterface $container) {
|
ConnectionPoolInterface::class => function (ContainerInterface $container) {
|
||||||
$config = $container->get('config')->get('database');
|
$config = $container->get('config')->get('database');
|
||||||
$connectionString = sprintf(
|
|
||||||
"%s:%s@%s/%s",
|
$pool = new ConnectionPool(new ConnectionPoolConfig(
|
||||||
rawurlencode($config['username']),
|
$config['username'],
|
||||||
rawurlencode($config['password']),
|
$config['password'],
|
||||||
$config['host'],
|
$config['host'],
|
||||||
$config['database']
|
$config['database']
|
||||||
);
|
));
|
||||||
return new MysqlClient($connectionString);
|
|
||||||
|
return $pool;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,7 +153,7 @@ class Bootstrap
|
|||||||
*/
|
*/
|
||||||
UserRepositoryInterface::class => function (ContainerInterface $container) {
|
UserRepositoryInterface::class => function (ContainerInterface $container) {
|
||||||
return new UserRepository(
|
return new UserRepository(
|
||||||
$container->get(MysqlClient::class),
|
$container->get(ConnectionPoolInterface::class),
|
||||||
$container->get(UserAuthorizationInterface::class)
|
$container->get(UserAuthorizationInterface::class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,21 @@
|
|||||||
|
|
||||||
namespace Slovocast\Controller;
|
namespace Slovocast\Controller;
|
||||||
|
|
||||||
use Slovocast\Controller\Controller;
|
use Slovocast\Domain\Repository\Channel\ChannelRepositoryInterface;
|
||||||
use Slovocast\Domain\Repository\User\UserRepositoryInterface;
|
use Slovocast\Domain\Repository\User\UserRepositoryInterface;
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
|
||||||
class DashboardPage extends Controller
|
class DashboardPage extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected UserRepositoryInterface $userRepository
|
protected UserRepositoryInterface $userRepository,
|
||||||
|
protected ChannelRepositoryInterface $channelRepository
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public function handle(): Response
|
public function handle(): Response
|
||||||
{
|
{
|
||||||
|
// get the user details
|
||||||
|
// get the channels
|
||||||
return $this->render('dashboard.twig');
|
return $this->render('dashboard.twig');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
namespace Slovocast\Controller\User;
|
namespace Slovocast\Controller\User;
|
||||||
|
|
||||||
|
use Odan\Session\SessionInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Slovocast\Controller\Controller;
|
use Slovocast\Controller\Controller;
|
||||||
use Slovocast\Domain\Repository\User\UserRepositoryInterface;
|
use Slovocast\Domain\Repository\User\UserRepositoryInterface;
|
||||||
use Slovocast\Infrastructure\User\UserAuthorizationInterface;
|
|
||||||
use Slovocast\Exception\EntityNotFoundException;
|
use Slovocast\Exception\EntityNotFoundException;
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Slovocast\Infrastructure\Api\User\UserAuthorizationInterface;
|
||||||
use Odan\Session\SessionInterface;
|
|
||||||
|
|
||||||
class LoginUserAction extends Controller
|
class LoginUserAction extends Controller
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php
|
<?php /** @noinspection ALL */
|
||||||
|
|
||||||
namespace Slovocast\Domain\Repository\Channel;
|
namespace Slovocast\Domain\Repository\Channel;
|
||||||
|
|
||||||
@ -95,9 +95,11 @@ class ChannelRepository implements ChannelRepositoryInterface
|
|||||||
*/
|
*/
|
||||||
public function create(Channel $channel, User $owner): int
|
public function create(Channel $channel, User $owner): int
|
||||||
{
|
{
|
||||||
$query = "INSERT INTO channels (name, slug, description, link,
|
$query = <<<SQL
|
||||||
language, copyright, explicit, owner_id)
|
INSERT INTO channels (name, slug, description, link,
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
language, copyright, explicit, owner_id)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
SQL;
|
||||||
|
|
||||||
$results = await($this->db->query($query, [
|
$results = await($this->db->query($query, [
|
||||||
$channel->getName(),
|
$channel->getName(),
|
||||||
@ -120,13 +122,16 @@ class ChannelRepository implements ChannelRepositoryInterface
|
|||||||
*/
|
*/
|
||||||
public function update(Channel $channel): bool
|
public function update(Channel $channel): bool
|
||||||
{
|
{
|
||||||
$query = "UPDATE channels SET name = ?,
|
$query = <<<SQL
|
||||||
slug = ?,
|
UPDATE channels SET name = ?,
|
||||||
description = ?,
|
slug = ?,
|
||||||
link = ?,
|
description = ?,
|
||||||
language = ?,
|
link = ?,
|
||||||
copyright = ?,
|
language = ?,
|
||||||
explicit = ?";
|
copyright = ?,
|
||||||
|
explicit = ?
|
||||||
|
WHERE id = ?;
|
||||||
|
SQL;
|
||||||
|
|
||||||
$results = await($this->db->query($query, [
|
$results = await($this->db->query($query, [
|
||||||
$channel->getName(),
|
$channel->getName(),
|
||||||
@ -136,6 +141,7 @@ class ChannelRepository implements ChannelRepositoryInterface
|
|||||||
$channel->getLanguage(),
|
$channel->getLanguage(),
|
||||||
$channel->getCopyright(),
|
$channel->getCopyright(),
|
||||||
$channel->isExplicit(),
|
$channel->isExplicit(),
|
||||||
|
$channel->getId(),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
return (bool) $results->affectedRows;
|
return (bool) $results->affectedRows;
|
||||||
|
@ -2,16 +2,16 @@
|
|||||||
|
|
||||||
namespace Slovocast\Domain\Repository\User;
|
namespace Slovocast\Domain\Repository\User;
|
||||||
|
|
||||||
use function React\Async\await;
|
|
||||||
use React\Mysql\MysqlClient;
|
|
||||||
use Slovocast\Infrastructure\User\UserAuthorizationInterface;
|
|
||||||
use Slovocast\Exception\EntityNotFoundException;
|
|
||||||
use Slovocast\Domain\Entity\User;
|
use Slovocast\Domain\Entity\User;
|
||||||
|
use Slovocast\Exception\EntityNotFoundException;
|
||||||
|
use Slovocast\Infrastructure\Api\Database\ConnectionPoolInterface;
|
||||||
|
use Slovocast\Infrastructure\Api\User\UserAuthorizationInterface;
|
||||||
|
use function React\Async\await;
|
||||||
|
|
||||||
class UserRepository implements UserRepositoryInterface
|
class UserRepository implements UserRepositoryInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private MysqlClient $db,
|
private ConnectionPoolInterface $connectionPool,
|
||||||
private UserAuthorizationInterface $userAuth
|
private UserAuthorizationInterface $userAuth
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -38,7 +38,11 @@ class UserRepository implements UserRepositoryInterface
|
|||||||
public function get(int $id): User
|
public function get(int $id): User
|
||||||
{
|
{
|
||||||
$query = "SELECT * FROM users WHERE id = ? LIMIT 1";
|
$query = "SELECT * FROM users WHERE id = ? LIMIT 1";
|
||||||
$results = await($this->db->query($query, [ $id ]));
|
|
||||||
|
/** @var $conn ConnectionPool */
|
||||||
|
$conn = $this->connectionPool->getConnection();
|
||||||
|
$results = await($conn->query($query, [ $id ]));
|
||||||
|
|
||||||
return $this->userFromQueryResults($results->resultRows[0]);
|
return $this->userFromQueryResults($results->resultRows[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slovocast\Infrastructure\Api\Database;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
interface ConnectionPoolInterface
|
||||||
|
{
|
||||||
|
public function getTotalIdleConnections(): int;
|
||||||
|
public function getTotalActiveConnections(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is at least one idle connection waiting to be used.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasIdleConnection(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the new wait timeout for acquiring a connection.
|
||||||
|
*
|
||||||
|
* @param int $ms The amount of time in seconds.
|
||||||
|
*/
|
||||||
|
public function setWaitTimeout(int $s): void;
|
||||||
|
public function getWaitTimeout(): int;
|
||||||
|
public function getConnectionLimit(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will grab a connection from the Idle list, push it into the Active list and returns that same
|
||||||
|
* connection to the caller. If there is no active connections, the method will wait based on the `waitTimeout`
|
||||||
|
* value (in seconds) and throw an exception when the timeout has been reached.
|
||||||
|
*
|
||||||
|
* @throws Exception When the Wait Timeout is surpassed waiting for an idle connection
|
||||||
|
* @return PooledConnectionInterface
|
||||||
|
*/
|
||||||
|
public function getConnection(): PooledConnectionInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will return a connection from the Active list into the Idle list.
|
||||||
|
*
|
||||||
|
* @param PooledConnectionInterface $connection
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function releaseConnection(PooledConnectionInterface $connection): void;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slovocast\Infrastructure\Api\Database;
|
||||||
|
|
||||||
|
interface PooledConnectionInterface
|
||||||
|
{
|
||||||
|
public function release(): void;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Slovocast\Infrastructure\User;
|
namespace Slovocast\Infrastructure\Api\User;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple interface for securing and checking secured passwords for a user
|
* A simple interface for securing and checking secured passwords for a user
|
89
app/src/Infrastructure/Database/ConnectionPool.php
Normal file
89
app/src/Infrastructure/Database/ConnectionPool.php
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slovocast\Infrastructure\Database;
|
||||||
|
|
||||||
|
use Slovocast\Infrastructure\Api\Database\ConnectionPoolInterface;
|
||||||
|
use Slovocast\Infrastructure\Api\Database\PooledConnectionInterface;
|
||||||
|
use SplObjectStorage;
|
||||||
|
|
||||||
|
class ConnectionPool implements ConnectionPoolInterface
|
||||||
|
{
|
||||||
|
private int $waitTimeout;
|
||||||
|
private SplObjectStorage $idleConnections;
|
||||||
|
private SplObjectStorage $activeConnections;
|
||||||
|
private ConnectionPoolConfig $config;
|
||||||
|
|
||||||
|
public function __construct(ConnectionPoolConfig $config)
|
||||||
|
{
|
||||||
|
$this->idleConnections = new SplObjectStorage();
|
||||||
|
$this->activeConnections = new SplObjectStorage();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $this->config->getTotalConnections(); $i++) {
|
||||||
|
$pooledConnection = new PooledConnection($this->config->getDsnString());
|
||||||
|
$this->idleConnections->attach($$pooledConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->waitTimeout = $config->getPoolWaitTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalIdleConnections(): int
|
||||||
|
{
|
||||||
|
return $this->idleConnections->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalActiveConnections(): int
|
||||||
|
{
|
||||||
|
return $this->activeConnections->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasIdleConnection(): bool
|
||||||
|
{
|
||||||
|
return $this->idleConnections->count() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setWaitTimeout(int $s): void
|
||||||
|
{
|
||||||
|
$this->waitTimeout = $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWaitTimeout(): int
|
||||||
|
{
|
||||||
|
return $this->waitTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnectionLimit(): int
|
||||||
|
{
|
||||||
|
return $this->config->getTotalConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @TODO Throw an exception when a total timeout is exceeded. We do not want
|
||||||
|
* to get into an infinite loop.
|
||||||
|
*
|
||||||
|
* @return PooledConnectionInterface
|
||||||
|
*/
|
||||||
|
public function getConnection(): PooledConnectionInterface
|
||||||
|
{
|
||||||
|
if (!$this->hasIdleConnection()) {
|
||||||
|
\React\Async\delay((float) $this->getWaitTimeout());
|
||||||
|
return $this->getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from the idle pool
|
||||||
|
$conn = $this->idleConnections->current();
|
||||||
|
$this->idleConnections->detach($conn);
|
||||||
|
|
||||||
|
// Attach to the pool to the connectin, add it to the active pool
|
||||||
|
$conn->setConnectionPool($this);
|
||||||
|
$this->activeConnections->attach($conn);
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function releaseConnection(PooledConnectionInterface $connection): void
|
||||||
|
{
|
||||||
|
if ($this->activeConnections->contains($connection)) {
|
||||||
|
$this->activeConnections->detach($connection);
|
||||||
|
$this->idleConnections->attach($connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
app/src/Infrastructure/Database/ConnectionPoolConfig.php
Normal file
45
app/src/Infrastructure/Database/ConnectionPoolConfig.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slovocast\Infrastructure\Database;
|
||||||
|
|
||||||
|
class ConnectionPoolConfig
|
||||||
|
{
|
||||||
|
const DEFAULT_WAIT_TIMEOUT = 60;
|
||||||
|
const DEFAULT_TOTAL_CONNECTIONS = 10;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public readonly string $username,
|
||||||
|
public readonly string $password,
|
||||||
|
public readonly string $database,
|
||||||
|
public readonly string $host,
|
||||||
|
protected int $port = 3306,
|
||||||
|
protected int $poolWaitTimeout = self::DEFAULT_WAIT_TIMEOUT,
|
||||||
|
protected int $totalConnections = self::DEFAULT_TOTAL_CONNECTIONS
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getPort(): int
|
||||||
|
{
|
||||||
|
return $this->port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPoolWaitTimeout(): int
|
||||||
|
{
|
||||||
|
return $this->poolWaitTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalConnections(): int
|
||||||
|
{
|
||||||
|
return $this->totalConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDsnString(): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
"%s:%s@%s/%s",
|
||||||
|
rawurlencode($this->username),
|
||||||
|
rawurlencode($this->password),
|
||||||
|
$this->host,
|
||||||
|
$this->database
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
23
app/src/Infrastructure/Database/PooledConnection.php
Normal file
23
app/src/Infrastructure/Database/PooledConnection.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slovocast\Infrastructure\Database;
|
||||||
|
|
||||||
|
use React\Mysql\MysqlClient;
|
||||||
|
use Slovocast\Infrastructure\Api\Database\ConnectionPoolInterface;
|
||||||
|
use Slovocast\Infrastructure\Api\Database\PooledConnectionInterface;
|
||||||
|
|
||||||
|
class PooledConnection extends MysqlClient implements PooledConnectionInterface
|
||||||
|
{
|
||||||
|
protected ConnectionPoolInterface $connectionPool;
|
||||||
|
|
||||||
|
public function setConnectionPool(ConnectionPoolInterface $connectionPool): self
|
||||||
|
{
|
||||||
|
$this->connectionPool = $connectionPool;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function release(): void
|
||||||
|
{
|
||||||
|
$this->connectionPool->releaseConnection($this);
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Slovocast\Infrastructure\User;
|
namespace Slovocast\Infrastructure\User;
|
||||||
|
|
||||||
use Slovocast\Infrastructure\User\UserAuthorizationInterface;
|
use Slovocast\Infrastructure\Api\User\UserAuthorizationInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This empty class will essentially just check hashed passwords passed into it
|
* This empty class will essentially just check hashed passwords passed into it
|
||||||
|
@ -25,11 +25,11 @@ class Routes
|
|||||||
$app->get('/healthcheck', HealthCheck::class);
|
$app->get('/healthcheck', HealthCheck::class);
|
||||||
// User Routes
|
// User Routes
|
||||||
self::users($app);
|
self::users($app);
|
||||||
|
self::dashboard($app);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param App $app Instantiated Application
|
* @param App $app Instantiated Application
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
protected static function users(App $app): void
|
protected static function users(App $app): void
|
||||||
{
|
{
|
||||||
@ -44,7 +44,13 @@ class Routes
|
|||||||
$app->post('/login', LoginUserAction::class)
|
$app->post('/login', LoginUserAction::class)
|
||||||
->add(AuthenticatedMiddleware::class)
|
->add(AuthenticatedMiddleware::class)
|
||||||
->setName('user-login-action');
|
->setName('user-login-action');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param App $app Instance of the application
|
||||||
|
*/
|
||||||
|
protected static function dashboard(App $app): void
|
||||||
|
{
|
||||||
$app->get('/dashboard', DashboardPage::class)
|
$app->get('/dashboard', DashboardPage::class)
|
||||||
->add(AuthenticatedMiddleware::class)
|
->add(AuthenticatedMiddleware::class)
|
||||||
->setName('user-dashboard');
|
->setName('user-dashboard');
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{% include 'components/flash.twig' %}
|
{% include 'layouts/components/flash.twig' %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
16
deploy/php/Dockerfile
Normal file
16
deploy/php/Dockerfile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
ARG PHP_VERSION=8.3
|
||||||
|
|
||||||
|
FROM chialab/php:${PHP_VERSION}
|
||||||
|
WORKDIR /app
|
||||||
|
COPY app .
|
||||||
|
RUN composer install \
|
||||||
|
--ignore-platform-reqs \
|
||||||
|
--no-interaction \
|
||||||
|
--no-plugins \
|
||||||
|
--prefer-dist \
|
||||||
|
--no-progress \
|
||||||
|
--optimize-autoloader
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD [ "php", "server/server.php" ]
|
@ -12,5 +12,12 @@ services:
|
|||||||
- "3306:3306"
|
- "3306:3306"
|
||||||
volumes:
|
volumes:
|
||||||
- slovocast_db:/var/lib/mysql/data/
|
- slovocast_db:/var/lib/mysql/data/
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: deploy/php/Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
slovocast_db:
|
slovocast_db:
|
||||||
|
Loading…
Reference in New Issue
Block a user