Skip to content

Commit

Permalink
add login; add users and actions; write some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jesperbeisner committed Sep 6, 2022
1 parent 61c2d3d commit 69d2bbf
Show file tree
Hide file tree
Showing 68 changed files with 1,379 additions and 100 deletions.
2 changes: 1 addition & 1 deletion .docker/caddy/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
root * /var/www/html/public
php_fastcgi php:9000
file_server
}
}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ docker-compose exec php php bin/console.php app:database-migration
docker-compose exec php php bin/console.php app:database-fixture

# Visit http://localhost:8080

# Test-Account-Mail: [email protected]
# Test-Account-Password: Password123
```

### Prod
Expand Down
2 changes: 2 additions & 0 deletions bin/console.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@

/** @var AbstractCommand $command */
$command = $serviceContainer->get($commandClass);
$command->setArguments($argv);

exit($command->execute());
11 changes: 5 additions & 6 deletions bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,19 @@

use Jesperbeisner\Fwstats\Stdlib\ServiceContainer;

const ROOT_DIR = __DIR__;
require __DIR__ . '/vendor/autoload.php';

require ROOT_DIR . '/vendor/autoload.php';
$config = require __DIR__ . '/config/config.php';

$config = require ROOT_DIR . '/config/config.php';

if (file_exists(ROOT_DIR . '/config/config.local.php')) {
$configLocal = require ROOT_DIR . '/config/config.local.php';
if (file_exists(__DIR__ . '/config/config.local.php')) {
$configLocal = require __DIR__ . '/config/config.local.php';
$config = array_merge($config, $configLocal);
}

$serviceContainer = new ServiceContainer($config['services']);

$serviceContainer->set('config', $config);
$serviceContainer->set('appEnv', $config['app_env']);
$serviceContainer->set('rootDir', __DIR__);

return $serviceContainer;
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"scripts": {
"csfixer": "vendor/bin/php-cs-fixer fix --diff",
"phpunit": "vendor/bin/phpunit --do-not-cache-result --debug",
"phpunit": "vendor/bin/phpunit --do-not-cache-result",
"phpstan": "vendor/bin/phpstan",
"test": {
"csfixer": "@csfixer",
Expand Down
5 changes: 3 additions & 2 deletions config/commands.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
use Jesperbeisner\Fwstats\Command;

return [
Command\DatabaseMigrationCommand::class,
Command\DatabaseFixtureCommand::class,
Command\AppCommand::class,
Command\CreateUserCommand::class,
Command\DatabaseFixtureCommand::class,
Command\DatabaseMigrationCommand::class,
];
14 changes: 12 additions & 2 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,18 @@
'controller' => [Controller\PingController::class, 'ping'],
],
[
'route' => '/logs',
'route' => '/admin/logs',
'methods' => ['GET'],
'controller' => [Controller\LogsController::class, 'logs'],
'controller' => [Controller\LogController::class, 'logs'],
],
[
'route' => '/login',
'methods' => ['GET', 'POST'],
'controller' => [Controller\SecurityController::class, 'login'],
],
[
'route' => '/logout',
'methods' => ['GET', 'POST'],
'controller' => [Controller\SecurityController::class, 'logout'],
],
];
16 changes: 13 additions & 3 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

declare(strict_types=1);

use Jesperbeisner\Fwstats\Action;
use Jesperbeisner\Fwstats\Command;
use Jesperbeisner\Fwstats\Controller;
use Jesperbeisner\Fwstats\ImageService;
Expand All @@ -19,7 +20,11 @@
Controller\PlaytimeController::class => Controller\Factory\PlaytimeControllerFactory::class,
Controller\PingController::class => Controller\Factory\PingControllerFactory::class,
Controller\ChangeController::class => Controller\Factory\ChangeControllerFactory::class,
Controller\LogsController::class => Controller\Factory\LogsControllerFactory::class,
Controller\LogController::class => Controller\Factory\LogControllerFactory::class,
Controller\SecurityController::class => Controller\Factory\SecurityControllerFactory::class,

// Actions
Action\CreateUserAction::class => Action\Factory\CreateUserActionFactory::class,

// Services
Service\Interface\FreewarDumpServiceInterface::class => Service\Factory\FreewarDumpServiceFactory::class,
Expand All @@ -30,9 +35,10 @@
ImageService\RankingImageService::class => ImageService\Factory\RankingImageServiceFactory::class,

// Commands
Command\DatabaseMigrationCommand::class => Command\Factory\DatabaseMigrationCommandFactory::class,
Command\DatabaseFixtureCommand::class => Command\Factory\DatabaseFixtureCommandFactory::class,
Command\AppCommand::class => Command\Factory\AppCommandFactory::class,
Command\CreateUserCommand::class => Command\Factory\CreateUserCommandFactory::class,
Command\DatabaseFixtureCommand::class => Command\Factory\DatabaseFixtureCommandFactory::class,
Command\DatabaseMigrationCommand::class => Command\Factory\DatabaseMigrationCommandFactory::class,

// Importer
Importer\ClanImporter::class => Importer\Factory\ClanImporterFactory::class,
Expand All @@ -59,9 +65,13 @@
Repository\AchievementRepository::class => Repository\Factory\RepositoryFactory::class,
Repository\LogRepository::class => Repository\Factory\RepositoryFactory::class,

Repository\UserRepository::class => Repository\Factory\UserRepositoryFactory::class,

// Stdlib
PDO::class => Stdlib\Factory\PdoFactory::class,
LoggerInterface::class => Stdlib\Factory\LoggerFactory::class,
Stdlib\Request::class => Stdlib\Factory\RequestFactory::class,
Stdlib\Router::class => Stdlib\Factory\RouterFactory::class,
Stdlib\Interface\SessionInterface::class => Stdlib\Factory\SessionFactory::class,
Stdlib\Interface\DatabaseInterface::class => Stdlib\Factory\DatabaseFactory::class,
];
9 changes: 9 additions & 0 deletions migrations/500-users-table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS users (
uuid TEXT PRIMARY KEY,
email TEXT NOT NULL,
password TEXT NOT NULL,
created DATETIME NOT NULL
);

CREATE UNIQUE INDEX users_email_unique_index ON users(email);
CREATE INDEX users_created_index ON users(created);
2 changes: 0 additions & 2 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,3 @@ parameters:
- public
- src
- tests
ignoreErrors:
- '#Constant ROOT_DIR not found.#'
7 changes: 7 additions & 0 deletions public/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,11 @@ document.addEventListener('DOMContentLoaded', () => {
actionFreewarTabContent.classList.add('is-hidden');
});
}

const $infoTexts = document.querySelectorAll('.info-text');
$infoTexts.forEach((infoText) => {
setTimeout(() => {
infoText.classList.add('is-hidden');
}, 2500);
});
});
70 changes: 70 additions & 0 deletions src/Action/CreateUserAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Jesperbeisner\Fwstats\Action;

use DateTimeImmutable;
use Jesperbeisner\Fwstats\Action\Exception\ActionException;
use Jesperbeisner\Fwstats\Action\Interface\ActionInterface;
use Jesperbeisner\Fwstats\Action\Interface\ActionResultInterface;
use Jesperbeisner\Fwstats\Action\Result\CreateUserActionResult;
use Jesperbeisner\Fwstats\Helper\UuidV4;
use Jesperbeisner\Fwstats\Model\User;
use Jesperbeisner\Fwstats\Repository\UserRepository;

final class CreateUserAction implements ActionInterface
{
private string $email;
private string $password;

public function __construct(
private readonly UserRepository $userRepository
) {
}

public function configure(array $data): void
{
if (empty($data['email'])) {
throw new ActionException("No email set in the 'AbstractAction::configure' method.");
}

if (empty($data['password'])) {
throw new ActionException("No password set in the 'AbstractAction::configure' method.");
}

if (!is_string($data['email'])) {
throw new ActionException("The email set in the 'AbstractAction::configure' method is not a string.");
}

if (!is_string($data['password'])) {
throw new ActionException("The password set in the 'AbstractAction::configure' method is not a string.");
}

if (filter_var($data['email'], FILTER_VALIDATE_EMAIL) === false) {
throw new ActionException("The email '{$data['email']}' is not valid email address.");
}

if (strlen($data['password']) < 8) {
throw new ActionException("The password must be at least 8 characters long.");
}

$this->email = $data['email'];
$this->password = $data['password'];
}

public function run(): CreateUserActionResult
{
if (null !== $this->userRepository->findOneByEmail($this->email)) {
throw new ActionException("A user with email '$this->email' already exists.");
}

$hashedPassword = password_hash($this->password, PASSWORD_DEFAULT);

$user = new User(UuidV4::create(), $this->email, $hashedPassword, new DateTimeImmutable());

$this->userRepository->insert($user);

return new CreateUserActionResult(ActionResultInterface::SUCCESS, ['user' => $user]);
}
}
11 changes: 11 additions & 0 deletions src/Action/Exception/ActionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Jesperbeisner\Fwstats\Action\Exception;

use RuntimeException;

final class ActionException extends RuntimeException
{
}
11 changes: 11 additions & 0 deletions src/Action/Exception/ActionResultException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Jesperbeisner\Fwstats\Action\Exception;

use RuntimeException;

final class ActionResultException extends RuntimeException
{
}
23 changes: 23 additions & 0 deletions src/Action/Factory/CreateUserActionFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Jesperbeisner\Fwstats\Action\Factory;

use Jesperbeisner\Fwstats\Action\CreateUserAction;
use Jesperbeisner\Fwstats\Repository\UserRepository;
use Jesperbeisner\Fwstats\Stdlib\Interface\FactoryInterface;
use Psr\Container\ContainerInterface;

class CreateUserActionFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $serviceContainer, string $serviceName): CreateUserAction
{
/** @var UserRepository $userRepository */
$userRepository = $serviceContainer->get(UserRepository::class);

return new CreateUserAction(
$userRepository,
);
}
}
15 changes: 15 additions & 0 deletions src/Action/Interface/ActionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Jesperbeisner\Fwstats\Action\Interface;

interface ActionInterface
{
/**
* @param mixed[] $data
*/
public function configure(array $data): void;

public function run(): ActionResultInterface;
}
20 changes: 20 additions & 0 deletions src/Action/Interface/ActionResultInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Jesperbeisner\Fwstats\Action\Interface;

interface ActionResultInterface
{
public const SUCCESS = 0;
public const FAILURE = 1;

public function isSuccess(): bool;

/**
* @return mixed[]
*/
public function getData(): array;

public function getMessage(): string;
}
45 changes: 45 additions & 0 deletions src/Action/Result/AbstractActionResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Jesperbeisner\Fwstats\Action\Result;

use Jesperbeisner\Fwstats\Action\Exception\ActionResultException;
use Jesperbeisner\Fwstats\Action\Interface\ActionResultInterface;

abstract class AbstractActionResult implements ActionResultInterface
{
protected readonly int $result;

/** @var mixed[] */
protected readonly array $data;
protected readonly string $message;

/**
* @param mixed[] $data
*/
public function __construct(int $result, array $data = [], string $message = '')
{
if (!in_array($result, [self::SUCCESS, self::FAILURE], true)) {
throw new ActionResultException('Only 0 and 1 are valid values for $result.');
}

$this->result = $result;
$this->data = $data;
$this->message = $message;
}

public function isSuccess(): bool
{
return $this->result === self::SUCCESS;
}
public function getData(): array
{
return $this->data;
}

public function getMessage(): string
{
return $this->message;
}
}
20 changes: 20 additions & 0 deletions src/Action/Result/CreateUserActionResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Jesperbeisner\Fwstats\Action\Result;

use Jesperbeisner\Fwstats\Action\Exception\ActionResultException;
use Jesperbeisner\Fwstats\Model\User;

final class CreateUserActionResult extends AbstractActionResult
{
public function getUser(): User
{
if (isset($this->data['user']) && $this->data['user'] instanceof User) {
return $this->data['user'];
}

throw new ActionResultException('No user in data array available.');
}
}
Loading

0 comments on commit 69d2bbf

Please sign in to comment.