From 5737bd6e6b31631984c75211820ba8848e3148ff Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Fri, 10 Nov 2023 15:26:32 +0100 Subject: [PATCH 01/38] Add start of homepage and FrontControler (don't run it) --- Documentation/php.puml | 8 ++++++ public/index.php | 23 ++++++------------ src/Controller/FrontController.php | 39 ++++++++++++++++++++++++++++++ src/Controller/UserController.php | 0 4 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 Documentation/php.puml create mode 100644 src/Controller/FrontController.php create mode 100644 src/Controller/UserController.php diff --git a/Documentation/php.puml b/Documentation/php.puml new file mode 100644 index 0000000..8641900 --- /dev/null +++ b/Documentation/php.puml @@ -0,0 +1,8 @@ +@startuml + +class FrontController { + - router : AltoRouter + +} + +@enduml \ No newline at end of file diff --git a/public/index.php b/public/index.php index 4c5290b..d1fee44 100644 --- a/public/index.php +++ b/public/index.php @@ -34,19 +34,12 @@ $con = new Connexion(get_database()); $router = new AltoRouter(); $router->setBasePath($basePath); -$sampleFormController = new SampleFormController(new FormResultGateway($con), $twig); -$router->map("GET", "/", fn() => $sampleFormController->displayForm()); -$router->map("POST", "/submit", fn() => $sampleFormController->submitForm($_POST)); -$router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); -$router->map("POST", "/submit-twig", fn() => $sampleFormController->submitFormTwig($_POST)); - -$match = $router->match(); - -if ($match == null) { - // TODO redirect to a 404 not found page instead (issue #1) - http_response_code(404); - echo "Page non trouvée"; - exit(1); -} +$frontController = new FrontController($router); + +//$sampleFormController = new SampleFormController(new FormResultGateway($con), $twig); + + + + + -call_user_func($match['target']); diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php new file mode 100644 index 0000000..dcee0b1 --- /dev/null +++ b/src/Controller/FrontController.php @@ -0,0 +1,39 @@ +router = $router; + } + + public function main() { + $this->toRoute(); + + } + + private function toRoute(){ + $this->router->map("GET", "/", fn() => $this->userControler->home()); +// $this->router->map("POST", "/submit", fn() => $sampleFormController->submitForm($_POST)); +// $this->router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); +// $this->router->map("POST", "/submit-twig", fn() => $sampleFormController->submitFormTwig($_POST)); + + $match = $router->match(); + + // À remplacer par l'appel du contrôler d'erreur + if ($match == null) { + // TODO redirect to a 404 not found page instead (issue #1) + http_response_code(404); + echo "Page non trouvée"; + exit(1); + } + + call_user_func($match['target']); + } + + +} \ No newline at end of file diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..e69de29 From ec474617035ab589fdc5e6ee2fe385c40b58b78f Mon Sep 17 00:00:00 2001 From: d_yanis Date: Tue, 14 Nov 2023 15:57:24 +0100 Subject: [PATCH 02/38] wip FRONT CONTROLLER --- composer.json | 1 + public/index.php | 11 +++-------- src/Controller/FrontController.php | 8 +++++--- src/Controller/UserController.php | 13 +++++++++++++ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index c78fb15..0f5e1f2 100644 --- a/composer.json +++ b/composer.json @@ -11,4 +11,5 @@ "ext-pdo_sqlite": "*", "twig/twig":"^2.0" } + } \ No newline at end of file diff --git a/public/index.php b/public/index.php index d1fee44..d4e227e 100644 --- a/public/index.php +++ b/public/index.php @@ -3,6 +3,7 @@ require "../vendor/autoload.php"; require "../config.php"; require "../sql/database.php"; +require "../src/Controller/FrontController.php"; use \Twig\Loader\FilesystemLoader; use App\Connexion; @@ -34,12 +35,6 @@ $con = new Connexion(get_database()); $router = new AltoRouter(); $router->setBasePath($basePath); -$frontController = new FrontController($router); - -//$sampleFormController = new SampleFormController(new FormResultGateway($con), $twig); - - - - - +$frontController = new FrontController($router, new UserController()); +//$sampleFormController = new SampleFormController(new FormResultGateway($con), $twig); \ No newline at end of file diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index dcee0b1..7a1179b 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -1,14 +1,16 @@ router = $router; + $this->userControler = $userControler; } public function main() { @@ -22,7 +24,7 @@ class FrontController{ // $this->router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); // $this->router->map("POST", "/submit-twig", fn() => $sampleFormController->submitFormTwig($_POST)); - $match = $router->match(); + $match = $this->router->match(); // À remplacer par l'appel du contrôler d'erreur if ($match == null) { diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index e69de29..0934a04 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -0,0 +1,13 @@ +Welcome!"; + } + +} +?> \ No newline at end of file From 02dbf269f966f6a0ded0cd01748342b9bd3e3b8d Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Fri, 10 Nov 2023 15:26:32 +0100 Subject: [PATCH 03/38] Add start of homepage and FrontControler (don't run it) Rebase modification --- Documentation/php.puml | 8 ++++ public/index.php | 51 ++------------------- src/Controller/FrontController.php | 72 ++++++++++++++++++++++++++++++ src/Controller/UserController.php | 0 4 files changed, 84 insertions(+), 47 deletions(-) create mode 100644 Documentation/php.puml create mode 100644 src/Controller/FrontController.php create mode 100644 src/Controller/UserController.php diff --git a/Documentation/php.puml b/Documentation/php.puml new file mode 100644 index 0000000..8641900 --- /dev/null +++ b/Documentation/php.puml @@ -0,0 +1,8 @@ +@startuml + +class FrontController { + - router : AltoRouter + +} + +@enduml \ No newline at end of file diff --git a/public/index.php b/public/index.php index ba9d7c0..9b36d85 100644 --- a/public/index.php +++ b/public/index.php @@ -18,8 +18,7 @@ use App\Validation\ValidationFail; use App\Controller\ErrorController; -$loader = new FilesystemLoader('../src/Views/'); -$twig = new \Twig\Environment($loader); + $basePath = get_public_path(); $con = new Connexion(get_database()); @@ -28,49 +27,7 @@ $con = new Connexion(get_database()); $router = new AltoRouter(); $router->setBasePath($basePath); -$sampleFormController = new SampleFormController(new FormResultGateway($con), $twig); -$editorController = new EditorController(new TacticModel(new TacticInfoGateway($con))); - - -$router->map("GET", "/", fn() => $sampleFormController->displayFormReact()); -$router->map("POST", "/submit", fn() => $sampleFormController->submitFormReact($_POST)); -$router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); -$router->map("POST", "/submit-twig", fn() => $sampleFormController->submitFormTwig($_POST)); -$router->map("GET", "/tactic/new", fn() => $editorController->makeNew()); -$router->map("GET", "/tactic/[i:id]/edit", fn(int $id) => $editorController->openEditorFor($id)); - -$match = $router->match(); - -if ($match == null) { - http_response_code(404); - ErrorController::displayFailures([ValidationFail::notFound("Cette page n'existe pas")], $twig); - return; -} - -$response = call_user_func_array($match['target'], $match['params']); - -http_response_code($response->getCode()); - -if ($response instanceof ViewHttpResponse) { - $file = $response->getFile(); - $args = $response->getArguments(); - - switch ($response->getViewKind()) { - case ViewHttpResponse::REACT_VIEW: - send_react_front($file, $args); - break; - case ViewHttpResponse::TWIG_VIEW: - try { - $twig->display($file, $args); - } catch (\Twig\Error\RuntimeError|\Twig\Error\SyntaxError $e) { - http_response_code(500); - echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); - throw e; - } - break; - } +//$sampleFormController = new SampleFormController(new FormResultGateway($con), $twig); +//$editorController = new EditorController(new TacticModel(new TacticInfoGateway($con))); -} else if ($response instanceof JsonHttpResponse) { - header('Content-type: application/json'); - echo $response->getJson(); -} \ No newline at end of file +$frontController = new FrontController($router); \ No newline at end of file diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php new file mode 100644 index 0000000..5963292 --- /dev/null +++ b/src/Controller/FrontController.php @@ -0,0 +1,72 @@ +router = $router; + } + + public function main() { + $this->toRoute(); + + } + + private function toRoute(){ + $this->router->map("GET", "/", fn() => $this->userControler->home()); +// $router->map("GET", "/", fn() => $sampleFormController->displayFormReact()); +// $router->map("POST", "/submit", fn() => $sampleFormController->submitFormReact($_POST)); +// $router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); +// $router->map("POST", "/submit-twig", fn() => $sampleFormController->submitFormTwig($_POST)); +// $router->map("GET", "/tactic/new", fn() => $editorController->makeNew()); +// $router->map("GET", "/tactic/[i:id]/edit", fn(int $id) => $editorController->openEditorFor($id)); + + + $match = $this->router->match(); + + if ($match == null) { + http_response_code(404); + ErrorController::displayFailures([ValidationFail::notFound("Cette page n'existe pas")], $twig); + return; + } + + $response = call_user_func_array($match['target'], $match['params']); + if ($response instanceof ViewHttpResponse) { + $file = $response->getFile(); + $args = $response->getArguments(); + + switch ($response->getViewKind()) { + case ViewHttpResponse::REACT_VIEW: + send_react_front($file, $args); + break; + case ViewHttpResponse::TWIG_VIEW: + try { + $loader = new FilesystemLoader('../src/Views/'); + $twig = new \Twig\Environment($loader); + $twig->display($file, $args); + } catch (\Twig\Error\RuntimeError|\Twig\Error\SyntaxError $e) { + http_response_code(500); + echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); + throw e; + } + break; + } + + } else if ($response instanceof JsonHttpResponse) { + header('Content-type: application/json'); + echo $response->getJson(); + } + + http_response_code($response->getCode()); + } + + +} \ No newline at end of file diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..e69de29 From 26a27c291d4d643f94f79b6fd22b5b48784b3b73 Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Tue, 14 Nov 2023 17:35:01 +0100 Subject: [PATCH 04/38] Add FrontController and UserController --- public/index.php | 7 +-- src/Controller/FrontController.php | 72 +++++++++++++++++++----------- src/Controller/UserController.php | 17 +++++++ src/Views/home.twig | 13 ++++++ 4 files changed, 80 insertions(+), 29 deletions(-) create mode 100644 src/Views/home.twig diff --git a/public/index.php b/public/index.php index 9b36d85..4ccdf44 100644 --- a/public/index.php +++ b/public/index.php @@ -6,6 +6,7 @@ require "../sql/database.php"; require "utils.php"; use App\Connexion; +use App\Controller\FrontController; use App\Controller\EditorController; use App\Controller\SampleFormController; use App\Gateway\FormResultGateway; @@ -18,8 +19,6 @@ use App\Validation\ValidationFail; use App\Controller\ErrorController; - - $basePath = get_public_path(); $con = new Connexion(get_database()); @@ -27,7 +26,9 @@ $con = new Connexion(get_database()); $router = new AltoRouter(); $router->setBasePath($basePath); +$frontController = new FrontController($router); +$frontController->route(); + //$sampleFormController = new SampleFormController(new FormResultGateway($con), $twig); //$editorController = new EditorController(new TacticModel(new TacticInfoGateway($con))); -$frontController = new FrontController($router); \ No newline at end of file diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 5963292..b43023e 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -1,27 +1,37 @@ router = $router; + $this->userController = new UserController(); } - public function main() { - $this->toRoute(); + public function run() { + $this->route(); } - private function toRoute(){ - $this->router->map("GET", "/", fn() => $this->userControler->home()); + public function route() + { + $this->router->map("GET", "/", fn() => $this->userController->home()); // $router->map("GET", "/", fn() => $sampleFormController->displayFormReact()); // $router->map("POST", "/submit", fn() => $sampleFormController->submitFormReact($_POST)); // $router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); @@ -33,39 +43,49 @@ class FrontController{ $match = $this->router->match(); if ($match == null) { - http_response_code(404); + $loader = new FilesystemLoader('../src/Views/'); + $twig = new \Twig\Environment($loader); + http_response_code(HttpCodes::NOT_FOUND); ErrorController::displayFailures([ValidationFail::notFound("Cette page n'existe pas")], $twig); return; } + $this->routeByResponseType($match); + } + private function routeByResponseType(array $match) + { $response = call_user_func_array($match['target'], $match['params']); + http_response_code($response->getCode()); if ($response instanceof ViewHttpResponse) { - $file = $response->getFile(); - $args = $response->getArguments(); - - switch ($response->getViewKind()) { - case ViewHttpResponse::REACT_VIEW: - send_react_front($file, $args); - break; - case ViewHttpResponse::TWIG_VIEW: - try { - $loader = new FilesystemLoader('../src/Views/'); - $twig = new \Twig\Environment($loader); - $twig->display($file, $args); - } catch (\Twig\Error\RuntimeError|\Twig\Error\SyntaxError $e) { - http_response_code(500); - echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); - throw e; - } - break; - } + + $this->displayByViewKind($response); } else if ($response instanceof JsonHttpResponse) { header('Content-type: application/json'); echo $response->getJson(); } + } - http_response_code($response->getCode()); + private function displayByViewKind(ViewHttpResponse $response){ + $file = $response->getFile(); + $args = $response->getArguments(); + + switch ($response->getViewKind()) { + case ViewHttpResponse::REACT_VIEW: + send_react_front($file, $args); + break; + case ViewHttpResponse::TWIG_VIEW: + try { + $loader = new FilesystemLoader('../src/Views/'); + $twig = new \Twig\Environment($loader); + $twig->display($file, $args); + } catch (\Twig\Error\RuntimeError | \Twig\Error\SyntaxError $e) { + http_response_code(500); + echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); + throw e; + } + break; + } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index e69de29..5a7d85a 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/src/Views/home.twig b/src/Views/home.twig new file mode 100644 index 0000000..8b5ce94 --- /dev/null +++ b/src/Views/home.twig @@ -0,0 +1,13 @@ + + + + + + + Document + + +

Test

+ + \ No newline at end of file From 051dbef9ed88597ea0baa94c2f10dae9c96beab2 Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Tue, 14 Nov 2023 17:37:23 +0100 Subject: [PATCH 05/38] Add FrontController and UserController --- src/Controller/FrontController.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index b43023e..c8b260d 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -26,7 +26,6 @@ class FrontController{ public function run() { $this->route(); - } public function route() @@ -49,10 +48,10 @@ class FrontController{ ErrorController::displayFailures([ValidationFail::notFound("Cette page n'existe pas")], $twig); return; } - $this->routeByResponseType($match); + $this->handleByResponseType($match); } - private function routeByResponseType(array $match) + private function handleByResponseType(array $match) { $response = call_user_func_array($match['target'], $match['params']); http_response_code($response->getCode()); From 61a6b5afd5c3e72706981171a0bd0b9be8d7e1b2 Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Fri, 17 Nov 2023 12:06:03 +0100 Subject: [PATCH 06/38] wip FrontController --- Documentation/http.puml | 2 +- public/index.php | 20 ++-- src/Controller/FrontController.php | 135 ++++++++++++++++++------ src/Controller/SampleFormController.php | 52 --------- src/Gateway/FormResultGateway.php | 33 ------ src/Views/home.twig | 2 +- src/Views/sample_form.html.twig | 20 ---- 7 files changed, 117 insertions(+), 147 deletions(-) delete mode 100644 src/Controller/SampleFormController.php delete mode 100644 src/Gateway/FormResultGateway.php delete mode 100644 src/Views/sample_form.html.twig diff --git a/Documentation/http.puml b/Documentation/http.puml index b41135d..9fbe606 100644 --- a/Documentation/http.puml +++ b/Documentation/http.puml @@ -35,7 +35,7 @@ class ViewHttpResponse extends HttpResponse { - arguments: array - kind: int - + __construct(kind: int, file: string, arguments: array, code: int = HttpCodes::OK) + - __construct(kind: int, file: string, arguments: array, code: int = HttpCodes::OK) + getViewKind(): int + getFile(): string + getArguments(): array diff --git a/public/index.php b/public/index.php index 77273f5..f95879f 100644 --- a/public/index.php +++ b/public/index.php @@ -4,6 +4,7 @@ require "../vendor/autoload.php"; require "../config.php"; require "../sql/database.php"; require "utils.php"; +require "../src/react-display.php"; use App\Connexion; @@ -19,18 +20,17 @@ use Twig\Loader\FilesystemLoader; use App\Validation\ValidationFail; use App\Controller\ErrorController; - +session_start(); $basePath = get_public_path(); +var_dump($basePath); $con = new Connexion(get_database()); -// routes initialization -$router = new AltoRouter(); -$router->setBasePath($basePath); - -$frontController = new FrontController($router); -$frontController->route(); - -//$sampleFormController = new SampleFormController(new FormResultGateway($con), $twig); -//$editorController = new EditorController(new TacticModel(new TacticInfoGateway($con))); +global $dictActionRole; +$dictActionRole = [ + "UserController" => "public", + "EditionUserController" => "editor" +]; +$frontController = new FrontController($con, $basePath, $dictActionRole); +$frontController->run(); diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 9a97466..337b3ca 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -3,13 +3,18 @@ namespace App\Controller; +use App\Connexion; use App\Controller\UserController; use AltoRouter; use App\Controller\ErrorController; +use App\Gateway\FormResultGateway; +use App\Gateway\TacticInfoGateway; use App\Http\HttpCodes; +use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; +use App\Model\TacticModel; use App\Validation\ValidationFail; use Twig\Loader\FilesystemLoader; @@ -17,44 +22,110 @@ use Twig\Loader\FilesystemLoader; class FrontController{ private AltoRouter $router; - private ?UserController $userController; + private Connexion $con; + private array $dictControllerRole; + public function __construct(Connexion $con, string $basePath, array $dictControllerRole) { + $this->con = $con; + $this->router = $this->createRouter($basePath); + $this->dictControllerRole = $dictControllerRole; + } + + /** + * Main behavior of the FrontController + * + * @return void + */ + public function run() : void { + $this->initializeRouterMap(); - public function __construct(AltoRouter $router){ - $this->router = $router; - $this->userController = new UserController(); + $match = $this->router->match(); + if ($this->validMatch($match)){ + $this->roleControl($match['target']); + } else { + $this->displayByViewKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND)); + } + // $this->roleControl($match["target"]); + // $this->handleByResponseType($this->matchRoute()); } - public function run() { - $this->route(); + /** + * Create a new instance of an AltoRouter + * + * @param string $basePath + * @return AltoRouter + */ + public function createRouter(string $basePath) : AltoRouter { + $router = new AltoRouter(); + $router->setBasePath($basePath); + return $router; } - public function route() - { - $this->router->map("GET", "/", fn() => $this->userController->home()); -// $router->map("GET", "/", fn() => $sampleFormController->displayFormReact()); -// $router->map("POST", "/submit", fn() => $sampleFormController->submitFormReact($_POST)); -// $router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); -// $router->map("POST", "/submit-twig", fn() => $sampleFormController->submitFormTwig($_POST)); -// $router->map("GET", "/tactic/new", fn() => $editorController->makeNew()); -// $router->map("GET", "/tactic/[i:id]/edit", fn(int $id) => $editorController->openEditorFor($id)); + /** + * Initialize project's routes + * + * @return void + */ + private function initializeRouterMap() : void { + $this->router->map("GET", "/", "UserController"); + $this->router->map("GET", "/[a:action]?", "UserController"); + $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "EditorController"); + // $this->router->map("GET", "/tactic/[i:id]/edit", "EditorController"); + // $this->router->map("GET", "/", fn() => (new UserController())->home()); + // $this->router->map("GET", "/tactic/new", fn() => (new EditorController(new TacticModel(new TacticInfoGateway($this->con))))->makeNew()); + // $this->router->map("GET", "/tactic/[i:id]/edit", fn(int $id) => (new EditorController(new TacticModel(new TacticInfoGateway($this->con))))->openEditorFor($id)); + } - $match = $this->router->match(); + private function validMatch($match){ + return $match != null; + } - if ($match == null) { - $loader = new FilesystemLoader('../src/Views/'); - $twig = new \Twig\Environment($loader); - http_response_code(HttpCodes::NOT_FOUND); - ErrorController::displayFailures([ValidationFail::notFound("Cette page n'existe pas")], $twig); - return; + /** + * Initialize router's settings + * + * @return ViewHttpResponse + */ + private function roleControl(string $actionController){ + + if (isset($_SESSION['role'])){ + if($_SESSION['role'] = $this->dictControllerRole[$actionController]){ + echo "Là on redirige vers le bon controller"; + } + } + else { + $_SESSION['role'] + echo "session initialisé"; } - $this->handleByResponseType($match); + + + + + + + + // // Page not found + // if ($match == null) { + // return new ViewHttpResponse(ViewHttpResponse::TWIG_VIEW, "Views/error.html.twig", [ValidationFail::notFound("Cette page n'existe pas")], HttpCodes::NOT_FOUND); + // } + // // $loader = new FilesystemLoader('../src/Views/'); + // // $twig = new \Twig\Environment($loader); + // // http_response_code(HttpCodes::NOT_FOUND); + // // ErrorController::displayFailures([ValidationFail::notFound("Cette page n'existe pas")], $twig); + // // return; + // // } + } - private function handleByResponseType(array $match) - { - $response = call_user_func_array($match['target'], $match['params']); + + /** + * Redirect the return of the response by the response's type + * + * @param array $match + * @return void + */ + private function handleByResponseType(HttpResponse $response) : void { + // $response = call_user_func_array($match['target'], $match['params']); http_response_code($response->getCode()); if ($response instanceof ViewHttpResponse) { @@ -66,7 +137,13 @@ class FrontController{ } } - private function displayByViewKind(ViewHttpResponse $response){ + /** + * Use the right method to display the response + * + * @param ViewHttpResponse $response + * @return void + */ + private function displayByViewKind(ViewHttpResponse $response) : void { $file = $response->getFile(); $args = $response->getArguments(); @@ -82,11 +159,9 @@ class FrontController{ } catch (\Twig\Error\RuntimeError | \Twig\Error\SyntaxError $e) { http_response_code(500); echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); - throw e; + throw $e; } break; } } - - } \ No newline at end of file diff --git a/src/Controller/SampleFormController.php b/src/Controller/SampleFormController.php deleted file mode 100644 index 4241ad4..0000000 --- a/src/Controller/SampleFormController.php +++ /dev/null @@ -1,52 +0,0 @@ -gateway = $gateway; - } - - - public function displayFormReact(): HttpResponse { - return ViewHttpResponse::react("views/SampleForm.tsx", []); - } - - public function displayFormTwig(): HttpResponse { - return ViewHttpResponse::twig('sample_form.html.twig', []); - } - - private function submitForm(array $form, callable $response): HttpResponse { - return Control::runCheckedFrom($form, [ - "name" => [Validators::lenBetween(0, 32), Validators::name("Le nom ne peut contenir que des lettres, des chiffres et des accents")], - "description" => [Validators::lenBetween(0, 512)] - ], function (HttpRequest $req) use ($response) { - $description = htmlspecialchars($req["description"]); - $this->gateway->insert($req["name"], $description); - $results = ["results" => $this->gateway->listResults()]; - return call_user_func_array($response, [$results]); - }, false); - } - - public function submitFormTwig(array $form): HttpResponse { - return $this->submitForm($form, fn(array $results) => ViewHttpResponse::twig('display_results.html.twig', $results)); - } - - public function submitFormReact(array $form): HttpResponse { - return $this->submitForm($form, fn(array $results) => ViewHttpResponse::react('views/DisplayResults.tsx', $results)); - } -} \ No newline at end of file diff --git a/src/Gateway/FormResultGateway.php b/src/Gateway/FormResultGateway.php deleted file mode 100644 index fe0c601..0000000 --- a/src/Gateway/FormResultGateway.php +++ /dev/null @@ -1,33 +0,0 @@ -con = $con; - } - - - function insert(string $username, string $description) { - $this->con->exec( - "INSERT INTO FormEntries VALUES (:name, :description)", - [ - ":name" => [$username, PDO::PARAM_STR], - "description" => [$description, PDO::PARAM_STR] - ] - ); - } - - function listResults(): array { - return $this->con->fetch("SELECT * FROM FormEntries", []); - } -} \ No newline at end of file diff --git a/src/Views/home.twig b/src/Views/home.twig index 8b5ce94..b65af07 100644 --- a/src/Views/home.twig +++ b/src/Views/home.twig @@ -8,6 +8,6 @@ Document -

Test

+

Page Home à faire

\ No newline at end of file diff --git a/src/Views/sample_form.html.twig b/src/Views/sample_form.html.twig deleted file mode 100644 index bcb958e..0000000 --- a/src/Views/sample_form.html.twig +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Twig view - - - -

Hello, this is a sample form made in Twig !

- -
- - - - - -
- - - \ No newline at end of file From 53584a518e44200be3f47bfc99cfc6191fab300f Mon Sep 17 00:00:00 2001 From: Override-6 Date: Tue, 21 Nov 2023 22:57:36 +0100 Subject: [PATCH 07/38] add salva-like routes and controller architecture --- src/Controller/ErrorController.php | 26 -------- src/Controller/FrontController.php | 2 +- src/Controller/SampleFormController.php | 64 ------------------- src/Controller/{ => Sub}/AuthController.php | 4 +- src/Controller/{ => Sub}/EditorController.php | 4 +- .../{ => Sub}/VisualizerController.php | 5 +- src/Controller/UserController.php | 36 ++++++++++- src/Model/AuthModel.php | 1 - 8 files changed, 40 insertions(+), 102 deletions(-) delete mode 100644 src/Controller/ErrorController.php delete mode 100644 src/Controller/SampleFormController.php rename src/Controller/{ => Sub}/AuthController.php (97%) rename src/Controller/{ => Sub}/EditorController.php (93%) rename src/Controller/{ => Sub}/VisualizerController.php (88%) diff --git a/src/Controller/ErrorController.php b/src/Controller/ErrorController.php deleted file mode 100644 index 7fc5239..0000000 --- a/src/Controller/ErrorController.php +++ /dev/null @@ -1,26 +0,0 @@ -display("error.html.twig", ['failures' => $failures]); - } catch (LoaderError|RuntimeError|SyntaxError $e) { - echo "Twig error: $e"; - } - } -} diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 66d5d4f..0600a01 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -56,7 +56,7 @@ class FrontController { private function initializeRouterMap(): void { $this->router->map("GET", "/", "UserController"); $this->router->map("GET", "/[a:action]?", "UserController"); - $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "EditorController"); + $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "UserController"); } /** diff --git a/src/Controller/SampleFormController.php b/src/Controller/SampleFormController.php deleted file mode 100644 index 773a4db..0000000 --- a/src/Controller/SampleFormController.php +++ /dev/null @@ -1,64 +0,0 @@ -gateway = $gateway; - } - - - public function displayFormReact(): HttpResponse { - return ViewHttpResponse::react("views/SampleForm.tsx", []); - } - - public function displayFormTwig(): HttpResponse { - return ViewHttpResponse::twig('sample_form.html.twig', []); - } - - /** - * @param array $form - * @param callable(array>): ViewHttpResponse $response - * @return HttpResponse - */ - private function submitForm(array $form, callable $response): HttpResponse { - return Control::runCheckedFrom($form, [ - "name" => [Validators::lenBetween(0, 32), Validators::name("Le nom ne peut contenir que des lettres, des chiffres et des accents")], - "description" => [Validators::lenBetween(0, 512)], - ], function (HttpRequest $req) use ($response) { - $description = htmlspecialchars($req["description"]); - $this->gateway->insert($req["name"], $description); - $results = ["results" => $this->gateway->listResults()]; - return call_user_func_array($response, [$results]); - }, false); - } - - /** - * @param array $form - * @return HttpResponse - */ - public function submitFormTwig(array $form): HttpResponse { - return $this->submitForm($form, fn(array $results) => ViewHttpResponse::twig('display_results.html.twig', $results)); - } - - /** - * @param array $form - * @return HttpResponse - */ - public function submitFormReact(array $form): HttpResponse { - return $this->submitForm($form, fn(array $results) => ViewHttpResponse::react('views/DisplayResults.tsx', $results)); - } -} diff --git a/src/Controller/AuthController.php b/src/Controller/Sub/AuthController.php similarity index 97% rename from src/Controller/AuthController.php rename to src/Controller/Sub/AuthController.php index e42c27d..a78b02e 100644 --- a/src/Controller/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -1,8 +1,7 @@ $tactic->getName(), "id" => $tactic->getId()]); } - public function create(): HttpResponse { + public function createNew(): HttpResponse { $tactic = $this->model->makeNewDefault(); return $this->openEditor($tactic); } diff --git a/src/Controller/VisualizerController.php b/src/Controller/Sub/VisualizerController.php similarity index 88% rename from src/Controller/VisualizerController.php rename to src/Controller/Sub/VisualizerController.php index 3c8b55e..e3b5663 100644 --- a/src/Controller/VisualizerController.php +++ b/src/Controller/Sub/VisualizerController.php @@ -1,6 +1,6 @@ tacticModel = $tacticModel; } - public function openVisualizer(int $id): HttpResponse { + public function visualize(int $id): HttpResponse { $tactic = $this->tacticModel->get($id); if ($tactic == null) { @@ -27,6 +27,5 @@ class VisualizerController { } return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]); - } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 930cd67..2a95bcc 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -2,15 +2,47 @@ namespace App\Controller; +use App\Connexion; +use App\Gateway\AuthGateway; +use App\Gateway\TacticInfoGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; +use App\Model\AuthModel; +use App\Model\TacticModel; class UserController { public function home(): HttpResponse { return ViewHttpResponse::twig("home.twig", []); } - public function default(): HttpResponse { - return self::home(); + public function register(): HttpResponse { + $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return (new Sub\AuthController($model))->displayRegister(); + } + return (new Sub\AuthController($model))->confirmRegister($_POST); + } + + public function login(): HttpResponse { + $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return (new Sub\AuthController($model))->displayLogin(); + } + return (new Sub\AuthController($model))->confirmLogin($_POST); + } + + public function open(int $id): HttpResponse { + $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + return (new Sub\VisualizerController($model))->visualize($id); + } + + public function edit(int $id): HttpResponse { + $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + return (new Sub\EditorController($model))->edit($id); + } + + public function create(): HttpResponse { + $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + return (new Sub\EditorController($model))->createNew(); } } diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index 45b63e4..312cca8 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -2,7 +2,6 @@ namespace App\Model; -use App\Controller\AuthController; use App\Gateway\AuthGateway; use App\Validation\FieldValidationFail; use App\Validation\ValidationFail; From 14612226cd1f9680776ad2de295a6e0afbc88b8f Mon Sep 17 00:00:00 2001 From: "vivien.dufour" Date: Wed, 22 Nov 2023 08:38:58 +0100 Subject: [PATCH 08/38] fix on routes --- src/Controller/FrontController.php | 49 ++++++++++++++----------- src/Controller/Sub/EditorController.php | 4 +- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 0600a01..58e5d9d 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -14,10 +14,12 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; use Twig\Loader\FilesystemLoader; -class FrontController { +class FrontController +{ private AltoRouter $router; - public function __construct(string $basePath) { + public function __construct(string $basePath) + { $this->router = $this->createRouter($basePath); $this->initializeRouterMap(); } @@ -27,7 +29,8 @@ class FrontController { * * @return void */ - public function run(): void { + public function run(): void + { $match = $this->router->match(); if ($match != null) { $this->handleMatch($match); @@ -42,7 +45,8 @@ class FrontController { * @param string $basePath * @return AltoRouter */ - public function createRouter(string $basePath): AltoRouter { + public function createRouter(string $basePath): AltoRouter + { $router = new AltoRouter(); $router->setBasePath($basePath); return $router; @@ -53,17 +57,19 @@ class FrontController { * * @return void */ - private function initializeRouterMap(): void { + private function initializeRouterMap(): void + { $this->router->map("GET", "/", "UserController"); - $this->router->map("GET", "/[a:action]?", "UserController"); - $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "UserController"); + $this->router->map("GET|POST", "/[a:action]?", "UserController"); + $this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); } /** * @param array $match * @return void */ - private function handleMatch(array $match): void { + private function handleMatch(array $match): void + { $tag = $match['target']; $action = $this->getAction($match); @@ -78,15 +84,12 @@ class FrontController { * @param array $params * @return HttpResponse */ - private function tryToCall(string $controller, string $action, array $params): HttpResponse { + private function tryToCall(string $controller, string $action, array $params): HttpResponse + { $controller = $this->getController($controller); - try { - if (is_callable([$controller, $action])) { - return call_user_func_array([$controller, $action], $params); - } else { - return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); - } - } catch (Exception $e) { + if (is_callable([$controller, $action])) { + return call_user_func_array([$controller, $action], $params); + } else { return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); } } @@ -97,7 +100,8 @@ class FrontController { * @param array $match * @return string */ - private function getAction(array $match): string { + private function getAction(array $match): string + { if (isset($match["params"]["action"])) { return $match["params"]["action"]; } @@ -110,7 +114,8 @@ class FrontController { * @param string $controller * @return mixed */ - private function getController(string $controller) { + private function getController(string $controller) + { $namespace = "\\App\\Controller\\"; $controller = $namespace . $controller; return new $controller(); @@ -122,7 +127,8 @@ class FrontController { * @param HttpResponse $response * @return void */ - private function handleResponseByType(HttpResponse $response): void { + private function handleResponseByType(HttpResponse $response): void + { http_response_code($response->getCode()); if ($response instanceof ViewHttpResponse) { $this->displayViewByKind($response); @@ -138,7 +144,8 @@ class FrontController { * @param ViewHttpResponse $response * @return void */ - private function displayViewByKind(ViewHttpResponse $response): void { + private function displayViewByKind(ViewHttpResponse $response): void + { $file = $response->getFile(); $args = $response->getArguments(); @@ -151,7 +158,7 @@ class FrontController { $loader = new FilesystemLoader('../src/Views/'); $twig = new Environment($loader); $twig->display($file, $args); - } catch (RuntimeError|SyntaxError|LoaderError $e) { + } catch (RuntimeError | SyntaxError | LoaderError $e) { http_response_code(500); echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); throw $e; diff --git a/src/Controller/Sub/EditorController.php b/src/Controller/Sub/EditorController.php index def6ac9..ce7a0a3 100644 --- a/src/Controller/Sub/EditorController.php +++ b/src/Controller/Sub/EditorController.php @@ -14,8 +14,8 @@ use App\Model\TacticModel; class EditorController { private TacticModel $model; - public function __construct() { - $this->model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + public function __construct(TacticModel $model) { + $this->model = $model; } private function openEditor(TacticInfo $tactic): HttpResponse { From 3d7eb7bbb10eb1a0012ed531dc19f760addf7af0 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Tue, 21 Nov 2023 19:40:30 +0100 Subject: [PATCH 09/38] add session handle --- Documentation/models.puml | 8 +-- public/index.php | 5 +- sql/setup-tables.sql | 24 +++++---- src/Controller/FrontController.php | 52 +++++++++---------- src/Controller/Sub/AuthController.php | 24 ++++----- src/Controller/UserController.php | 6 +-- src/Data/Account.php | 73 ++++----------------------- src/Gateway/AccountGateway.php | 56 ++++++++++++++++++++ src/Gateway/AuthGateway.php | 47 ----------------- src/Model/AuthModel.php | 66 ++++++++++++------------ src/Session/MutableSessionHandle.php | 9 ++++ src/Session/PhpSessionHandle.php | 24 +++++++++ src/Session/SessionHandle.php | 11 ++++ 13 files changed, 206 insertions(+), 199 deletions(-) create mode 100644 src/Gateway/AccountGateway.php delete mode 100644 src/Gateway/AuthGateway.php create mode 100644 src/Session/MutableSessionHandle.php create mode 100644 src/Session/PhpSessionHandle.php create mode 100644 src/Session/SessionHandle.php diff --git a/Documentation/models.puml b/Documentation/models.puml index d95343c..82037bc 100755 --- a/Documentation/models.puml +++ b/Documentation/models.puml @@ -81,7 +81,7 @@ AuthController --> "- model" AuthModel class AuthModel{ + register(username : string, password : string, confirmPassword : string, email : string): array - + getUserFields(email : string):array + + getAccount(email : string):array + login(email : string, password : string) } AuthModel --> "- gateway" AuthGateway @@ -89,9 +89,9 @@ AuthModel --> "- gateway" AuthGateway class AuthGateway{ -con : Connection - + mailExist(email : string) : bool + + mailExists(email : string) : bool + insertAccount(username : string, hash : string, email : string) - + getUserHash(email : string):string - + getUserFields (email : string): array + + getHash(email : string):string + + getAccount (email : string): array } @enduml \ No newline at end of file diff --git a/public/index.php b/public/index.php index f898d7f..ce1df33 100644 --- a/public/index.php +++ b/public/index.php @@ -1,5 +1,6 @@ run(); + +$frontController->run(PhpSessionHandle::init()); diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index 108b62a..a289336 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -1,18 +1,24 @@ - -- drop tables here DROP TABLE IF EXISTS FormEntries; -DROP TABLE IF EXISTS AccountUser; +DROP TABLE IF EXISTS Account; DROP TABLE IF EXISTS TacticInfo; -CREATE TABLE FormEntries(name varchar, description varchar); -CREATE TABLE AccountUser( +CREATE TABLE FormEntries +( + name varchar, + description varchar +); +CREATE TABLE Account +( username varchar, - hash varchar, - email varchar unique + hash varchar, + email varchar unique, + token varchar(256) NOT NULL UNIQUE ); -CREATE TABLE TacticInfo( - id integer PRIMARY KEY AUTOINCREMENT, - name varchar, +CREATE TABLE TacticInfo +( + id integer PRIMARY KEY AUTOINCREMENT, + name varchar, creation_date timestamp DEFAULT CURRENT_TIMESTAMP ); \ No newline at end of file diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 58e5d9d..d5c6417 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -7,6 +7,7 @@ use App\Http\HttpCodes; use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; +use App\Session\MutableSessionHandle; use Exception; use Twig\Environment; use Twig\Error\LoaderError; @@ -14,12 +15,10 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; use Twig\Loader\FilesystemLoader; -class FrontController -{ +class FrontController { private AltoRouter $router; - public function __construct(string $basePath) - { + public function __construct(string $basePath) { $this->router = $this->createRouter($basePath); $this->initializeRouterMap(); } @@ -29,11 +28,10 @@ class FrontController * * @return void */ - public function run(): void - { + public function run(MutableSessionHandle $session): void { $match = $this->router->match(); if ($match != null) { - $this->handleMatch($match); + $this->handleMatch($match, $session); } else { $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND)); } @@ -45,8 +43,7 @@ class FrontController * @param string $basePath * @return AltoRouter */ - public function createRouter(string $basePath): AltoRouter - { + public function createRouter(string $basePath): AltoRouter { $router = new AltoRouter(); $router->setBasePath($basePath); return $router; @@ -57,8 +54,7 @@ class FrontController * * @return void */ - private function initializeRouterMap(): void - { + private function initializeRouterMap(): void { $this->router->map("GET", "/", "UserController"); $this->router->map("GET|POST", "/[a:action]?", "UserController"); $this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); @@ -68,28 +64,34 @@ class FrontController * @param array $match * @return void */ - private function handleMatch(array $match): void - { + private function handleMatch(array $match, MutableSessionHandle $session): void { $tag = $match['target']; $action = $this->getAction($match); $params = $match["params"]; unset($params['action']); - $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params))); + $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params), $session)); } /** * @param string $controller * @param string $action * @param array $params + * @param MutableSessionHandle $session * @return HttpResponse */ - private function tryToCall(string $controller, string $action, array $params): HttpResponse - { + + private function tryToCall(string $controller, string $action, array $params, MutableSessionHandle $session): HttpResponse { $controller = $this->getController($controller); - if (is_callable([$controller, $action])) { - return call_user_func_array([$controller, $action], $params); - } else { + try { + if (is_callable([$controller, $action])) { + // append the session as the last parameter of a controller function + $params[] = $session; + return call_user_func_array([$controller, $action], $params); + } else { + return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + } catch (Exception $e) { return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); } } @@ -100,8 +102,7 @@ class FrontController * @param array $match * @return string */ - private function getAction(array $match): string - { + private function getAction(array $match): string { if (isset($match["params"]["action"])) { return $match["params"]["action"]; } @@ -114,8 +115,7 @@ class FrontController * @param string $controller * @return mixed */ - private function getController(string $controller) - { + private function getController(string $controller) { $namespace = "\\App\\Controller\\"; $controller = $namespace . $controller; return new $controller(); @@ -127,8 +127,7 @@ class FrontController * @param HttpResponse $response * @return void */ - private function handleResponseByType(HttpResponse $response): void - { + private function handleResponseByType(HttpResponse $response): void { http_response_code($response->getCode()); if ($response instanceof ViewHttpResponse) { $this->displayViewByKind($response); @@ -144,8 +143,7 @@ class FrontController * @param ViewHttpResponse $response * @return void */ - private function displayViewByKind(ViewHttpResponse $response): void - { + private function displayViewByKind(ViewHttpResponse $response): void { $file = $response->getFile(); $args = $response->getArguments(); diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/Sub/AuthController.php index a78b02e..7a537f2 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -2,6 +2,7 @@ namespace App\Controller\Sub; +use App\Gateway\AccountGateway; use App\Http\HttpRequest; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; @@ -49,17 +50,16 @@ class AuthController { "username" => [Validators::name(), Validators::lenBetween(2, 32)], "password" => [Validators::lenBetween(6, 256)], "confirmpassword" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"),Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_register.html.twig", $fails); } - $fails = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email']); - if (empty($fails)) { - $results = $this->model->getUserFields($request['email']); - return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $results['username'], 'email' => $results['email']]); + $account = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email'], $fails); + if (!empty($fails)) { + return $this->displayBadFields("display_register.html.twig", $fails); } - return $this->displayBadFields("display_register.html.twig", $fails); + return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); } @@ -75,18 +75,18 @@ class AuthController { $fails = []; $request = HttpRequest::from($request, $fails, [ "password" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"),Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_login.html.twig", $fails); } - $fails = $this->model->login($request['email'], $request['password']); - if (empty($fails)) { - $results = $this->model->getUserFields($request['email']); - return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $results['username'], 'email' => $results['email']]); + $account = $this->model->login($request['email'], $request['password'], $fails); + if (!empty($fails)) { + return $this->displayBadFields("display_login.html.twig", $fails); } - return $this->displayBadFields("display_login.html.twig", $fails); + + return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 2a95bcc..360c9a3 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -3,7 +3,7 @@ namespace App\Controller; use App\Connexion; -use App\Gateway\AuthGateway; +use App\Gateway\AccountGateway; use App\Gateway\TacticInfoGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; @@ -16,7 +16,7 @@ class UserController { } public function register(): HttpResponse { - $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\AuthController($model))->displayRegister(); } @@ -24,7 +24,7 @@ class UserController { } public function login(): HttpResponse { - $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\AuthController($model))->displayLogin(); } diff --git a/src/Data/Account.php b/src/Data/Account.php index 2a21bf1..8b58e1c 100755 --- a/src/Data/Account.php +++ b/src/Data/Account.php @@ -4,8 +4,6 @@ namespace App\Data; use http\Exception\InvalidArgumentException; -const PHONE_NUMBER_REGEXP = "/^\\+[0-9]+$/"; - /** * Base class of a user account. * Contains the private information that we don't want @@ -16,27 +14,16 @@ class Account { * @var string $email account's mail address */ private string $email; - /** - * @var string account's phone number. - * its format is specified by the {@link PHONE_NUMBER_REGEXP} constant - * - */ - private string $phoneNumber; /** - * @var AccountUser account's public and shared information + * @var string string token */ - private AccountUser $user; + private string $token; /** - * @var Team[] account's teams + * @var string the account's username */ - private array $teams; - - /** - * @var int account's unique identifier - */ - private int $id; + private string $name; /** @@ -46,60 +33,22 @@ class Account { * @param Team[] $teams * @param int $id */ - public function __construct(string $email, string $phoneNumber, AccountUser $user, array $teams, int $id) { + public function __construct(string $email, string $name, string $token) { $this->email = $email; - $this->phoneNumber = $phoneNumber; - $this->user = $user; - $this->teams = $teams; - $this->id = $id; + $this->name = $name; + $this->token = $token; } - /** - * @return string - */ public function getEmail(): string { return $this->email; } - /** - * @param string $email - */ - public function setEmail(string $email): void { - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException("Invalid mail address"); - } - $this->email = $email; - } - - /** - * @return string - */ - public function getPhoneNumber(): string { - return $this->phoneNumber; + public function getToken(): string { + return $this->token; } - /** - * @param string $phoneNumber - */ - public function setPhoneNumber(string $phoneNumber): void { - if (!preg_match(PHONE_NUMBER_REGEXP, $phoneNumber)) { - throw new InvalidArgumentException("Invalid phone number"); - } - $this->phoneNumber = $phoneNumber; + public function getName(): string { + return $this->name; } - public function getId(): int { - return $this->id; - } - - /** - * @return Team[] - */ - public function getTeams(): array { - return $this->teams; - } - - public function getUser(): AccountUser { - return $this->user; - } } diff --git a/src/Gateway/AccountGateway.php b/src/Gateway/AccountGateway.php new file mode 100644 index 0000000..a4686d6 --- /dev/null +++ b/src/Gateway/AccountGateway.php @@ -0,0 +1,56 @@ +con = $con; + } + + + public function exists(string $email): bool { + return $this->getAccount($email) != null; + } + + + public function insertAccount(Account $account, string $hash): void { + $this->con->exec("INSERT INTO Account VALUES (:username,:hash,:email)", [ + ':username' => [$account->getName(), PDO::PARAM_STR], + ':hash' => [$hash, PDO::PARAM_STR], + ':email' => [$account->getEmail(), PDO::PARAM_STR], + ':token' => [$account->getToken(), PDO::PARAM_STR] + ]); + } + + public function getHash(string $email): string { + $results = $this->con->fetch("SELECT hash FROM Account WHERE email = :email", [ + ':email' => [$email, PDO::PARAM_STR] + ]); + return $results[0]['hash']; + } + + + /** + * @param string $email + * @return Account|null + */ + public function getAccount(string $email): ?Account { + $results = $this->con->fetch("SELECT username,email,token FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); + if (empty($results)) + return null; + + $acc = $results[0]; + return new Account($acc["email"], $acc["name"], $acc["token"]); + } + + +} diff --git a/src/Gateway/AuthGateway.php b/src/Gateway/AuthGateway.php deleted file mode 100644 index 5acc01c..0000000 --- a/src/Gateway/AuthGateway.php +++ /dev/null @@ -1,47 +0,0 @@ -con = $con; - } - - - public function mailExist(string $email): bool { - return $this->getUserFields($email) != null; - } - - - public function insertAccount(string $username, string $hash, string $email): void { - $this->con->exec("INSERT INTO AccountUser VALUES (:username,:hash,:email)", [':username' => [$username, PDO::PARAM_STR],':hash' => [$hash, PDO::PARAM_STR],':email' => [$email, PDO::PARAM_STR]]); - } - - public function getUserHash(string $email): string { - $results = $this->con->fetch("SELECT hash FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); - return $results[0]['hash']; - } - - - /** - * @param string $email - * @return array|null - */ - public function getUserFields(string $email): ?array { - $results = $this->con->fetch("SELECT username,email FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); - $firstRow = $results[0] ?? null; - return $firstRow; - } - - - - -} diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index 312cca8..b786c2e 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -2,16 +2,19 @@ namespace App\Model; -use App\Gateway\AuthGateway; +use App\Controller\AuthController; +use App\Data\Account; +use App\Gateway\AccountGateway; use App\Validation\FieldValidationFail; use App\Validation\ValidationFail; class AuthModel { - private AuthGateway $gateway; + private AccountGateway $gateway; + /** - * @param AuthGateway $gateway + * @param AccountGateway $gateway */ - public function __construct(AuthGateway $gateway) { + public function __construct(AccountGateway $gateway) { $this->gateway = $gateway; } @@ -21,59 +24,54 @@ class AuthModel { * @param string $password * @param string $confirmPassword * @param string $email - * @return ValidationFail[] + * @param ValidationFail[] $failures + * @return Account|null the registered account or null if failures occurred */ - public function register(string $username, string $password, string $confirmPassword, string $email): array { - $errors = []; + public function register(string $username, string $password, string $confirmPassword, string $email, array &$failures): ?Account { if ($password != $confirmPassword) { - $errors[] = new FieldValidationFail("confirmpassword", "password and password confirmation are not equals"); + $failures[] = new FieldValidationFail("confirmpassword", "password and password confirmation are not equals"); } - if ($this->gateway->mailExist($email)) { - $errors[] = new FieldValidationFail("email", "email already exist"); + if ($this->gateway->exists($email)) { + $failures[] = new FieldValidationFail("email", "email already exist"); } - if(empty($errors)) { - $hash = password_hash($password, PASSWORD_DEFAULT); - $this->gateway->insertAccount($username, $hash, $email); + if (!empty($errors)) { + return null; } - return $errors; - } + $hash = password_hash($password, PASSWORD_DEFAULT); - /** - * @param string $email - * @return array|null - */ - public function getUserFields(string $email): ?array { - return $this->gateway->getUserFields($email); + $account = new Account($email, $username, $this->generateToken()); + $this->gateway->insertAccount($account, $hash); + return $account; } + private function generateToken(): string { + return base64_encode(random_bytes(64)); + } /** * @param string $email * @param string $password - * @return ValidationFail[] $errors + * @param ValidationFail[] $failures + * @return Account|null the authenticated account or null if failures occurred */ - public function login(string $email, string $password): array { - $errors = []; - - if (!$this->gateway->mailExist($email)) { - $errors[] = new FieldValidationFail("email", "email doesnt exists"); - return $errors; + public function login(string $email, string $password, array &$failures): ?Account { + if (!$this->gateway->exists($email)) { + $failures = new FieldValidationFail("email", "email doesnt exists"); + return null; } - $hash = $this->gateway->getUserHash($email); + $hash = $this->gateway->getHash($email); if (!password_verify($password, $hash)) { - $errors[] = new FieldValidationFail("password", "invalid password"); + $failures = new FieldValidationFail("password", "invalid password"); + return null; } - return $errors; + return $this->gateway->getAccount($email); } - - - } diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php new file mode 100644 index 0000000..a7822a3 --- /dev/null +++ b/src/Session/MutableSessionHandle.php @@ -0,0 +1,9 @@ + Date: Tue, 21 Nov 2023 21:30:12 +0100 Subject: [PATCH 10/38] front-controller (#17) I did the FRONT CONTROLLERRRRR. (Salva version lol) # When you create a new controller When you create a new controller, define the path to call it here (image 1). Don't forget to associate a controller with a role. # The action's main method The main method have to return a `ViewHttpResponse`. If you create a new action, please make sure that the action's main method have the same name than the action (image 2 and 3) and to make the main method public. Co-authored-by: DahmaneYanis Co-authored-by: d_yanis Co-authored-by: Override-6 Reviewed-on: https://codefirst.iut.uca.fr/git/IQBall/Application-Web/pulls/17 Co-authored-by: Yanis DAHMANE-BOUNOUA Co-committed-by: Yanis DAHMANE-BOUNOUA --- Documentation/http.puml | 2 +- Documentation/php.puml | 8 ++ public/index.php | 81 +------------- src/Controller/EditorController.php | 14 ++- src/Controller/FrontController.php | 162 ++++++++++++++++++++++++++++ src/Controller/UserController.php | 16 +++ src/Views/home.twig | 13 +++ 7 files changed, 210 insertions(+), 86 deletions(-) create mode 100644 Documentation/php.puml create mode 100644 src/Controller/FrontController.php create mode 100644 src/Controller/UserController.php create mode 100644 src/Views/home.twig diff --git a/Documentation/http.puml b/Documentation/http.puml index b41135d..9fbe606 100644 --- a/Documentation/http.puml +++ b/Documentation/http.puml @@ -35,7 +35,7 @@ class ViewHttpResponse extends HttpResponse { - arguments: array - kind: int - + __construct(kind: int, file: string, arguments: array, code: int = HttpCodes::OK) + - __construct(kind: int, file: string, arguments: array, code: int = HttpCodes::OK) + getViewKind(): int + getFile(): string + getArguments(): array diff --git a/Documentation/php.puml b/Documentation/php.puml new file mode 100644 index 0000000..8641900 --- /dev/null +++ b/Documentation/php.puml @@ -0,0 +1,8 @@ +@startuml + +class FrontController { + - router : AltoRouter + +} + +@enduml \ No newline at end of file diff --git a/public/index.php b/public/index.php index edc4cb3..f898d7f 100644 --- a/public/index.php +++ b/public/index.php @@ -4,83 +4,10 @@ require "../vendor/autoload.php"; require "../config.php"; require "../sql/database.php"; require "utils.php"; +require "../src/react-display.php"; -use App\Connexion; -use App\Controller\EditorController; -use App\Controller\SampleFormController; -use App\Gateway\FormResultGateway; -use App\Gateway\TacticInfoGateway; -use App\Http\JsonHttpResponse; -use App\Http\ViewHttpResponse; -use App\Model\TacticModel; -use Twig\Loader\FilesystemLoader; -use App\Gateway\AuthGateway; -use App\Controller\AuthController; -use App\Validation\ValidationFail; -use App\Controller\ErrorController; -use App\Controller\VisualizerController; - -$loader = new FilesystemLoader('../src/Views/'); -$twig = new \Twig\Environment($loader); +use App\Controller\FrontController; $basePath = get_public_path(); -$con = new Connexion(get_database()); - -// routes initialization -$router = new AltoRouter(); -$router->setBasePath($basePath); - -$sampleFormController = new SampleFormController(new FormResultGateway($con)); -$authGateway = new AuthGateway($con); -$authController = new \App\Controller\AuthController(new \App\Model\AuthModel($authGateway)); -$editorController = new EditorController(new TacticModel(new TacticInfoGateway($con))); -$visualizerController = new VisualizerController(new TacticModel(new TacticInfoGateway($con))); - - -$router->map("GET", "/", fn() => $sampleFormController->displayFormReact()); -$router->map("POST", "/submit", fn() => $sampleFormController->submitFormReact($_POST)); -$router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); -$router->map("POST", "/submit-twig", fn() => $sampleFormController->submitFormTwig($_POST)); -$router->map("GET", "/register", fn() => $authController->displayRegister()); -$router->map("POST", "/register", fn() => $authController->confirmRegister($_POST)); -$router->map("GET", "/login", fn() => $authController->displayLogin()); -$router->map("POST", "/login", fn() => $authController->confirmLogin($_POST)); -$router->map("GET", "/tactic/new", fn() => $editorController->makeNew()); -$router->map("GET", "/tactic/[i:id]/edit", fn(int $id) => $editorController->openEditorFor($id)); -$router->map("GET", "/tactic/[i:id]", fn(int $id) => $visualizerController->openVisualizer($id)); - -$match = $router->match(); - -if ($match == null) { - http_response_code(404); - ErrorController::displayFailures([ValidationFail::notFound("Cette page n'existe pas")], $twig); - return; -} - -$response = call_user_func_array($match['target'], $match['params']); - -http_response_code($response->getCode()); - -if ($response instanceof ViewHttpResponse) { - $file = $response->getFile(); - $args = $response->getArguments(); - - switch ($response->getViewKind()) { - case ViewHttpResponse::REACT_VIEW: - send_react_front($file, $args); - break; - case ViewHttpResponse::TWIG_VIEW: - try { - $twig->display($file, $args); - } catch (\Twig\Error\RuntimeError|\Twig\Error\SyntaxError $e) { - http_response_code(500); - echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); - throw $e; - } - break; - } - -} elseif ($response instanceof JsonHttpResponse) { - header('Content-type: application/json'); - echo $response->getJson(); -} +$frontController = new FrontController($basePath); +$frontController->run(); diff --git a/src/Controller/EditorController.php b/src/Controller/EditorController.php index ed270d1..eb07184 100644 --- a/src/Controller/EditorController.php +++ b/src/Controller/EditorController.php @@ -2,9 +2,10 @@ namespace App\Controller; +use App\Connexion; use App\Data\TacticInfo; +use App\Gateway\TacticInfoGateway; use App\Http\HttpCodes; -use App\Http\HttpRequest; use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; @@ -13,18 +14,15 @@ use App\Model\TacticModel; class EditorController { private TacticModel $model; - /** - * @param TacticModel $model - */ - public function __construct(TacticModel $model) { - $this->model = $model; + public function __construct() { + $this->model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); } private function openEditor(TacticInfo $tactic): HttpResponse { return ViewHttpResponse::react("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]); } - public function makeNew(): HttpResponse { + public function create(): HttpResponse { $tactic = $this->model->makeNewDefault(); return $this->openEditor($tactic); } @@ -34,7 +32,7 @@ class EditorController { * @param int $id the targeted tactic identifier * @return HttpResponse */ - public function openEditorFor(int $id): HttpResponse { + public function edit(int $id): HttpResponse { $tactic = $this->model->get($id); if ($tactic == null) { diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php new file mode 100644 index 0000000..66d5d4f --- /dev/null +++ b/src/Controller/FrontController.php @@ -0,0 +1,162 @@ +router = $this->createRouter($basePath); + $this->initializeRouterMap(); + } + + /** + * Main behavior of the FrontController + * + * @return void + */ + public function run(): void { + $match = $this->router->match(); + if ($match != null) { + $this->handleMatch($match); + } else { + $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND)); + } + } + + /** + * Create a new instance of an AltoRouter + * + * @param string $basePath + * @return AltoRouter + */ + public function createRouter(string $basePath): AltoRouter { + $router = new AltoRouter(); + $router->setBasePath($basePath); + return $router; + } + + /** + * Initialize project's routes + * + * @return void + */ + private function initializeRouterMap(): void { + $this->router->map("GET", "/", "UserController"); + $this->router->map("GET", "/[a:action]?", "UserController"); + $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "EditorController"); + } + + /** + * @param array $match + * @return void + */ + private function handleMatch(array $match): void { + $tag = $match['target']; + + $action = $this->getAction($match); + $params = $match["params"]; + unset($params['action']); + $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params))); + } + + /** + * @param string $controller + * @param string $action + * @param array $params + * @return HttpResponse + */ + private function tryToCall(string $controller, string $action, array $params): HttpResponse { + $controller = $this->getController($controller); + try { + if (is_callable([$controller, $action])) { + return call_user_func_array([$controller, $action], $params); + } else { + return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + } catch (Exception $e) { + return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + } + + /** + * Get the right method to call to do an action + * + * @param array $match + * @return string + */ + private function getAction(array $match): string { + if (isset($match["params"]["action"])) { + return $match["params"]["action"]; + } + return "default"; + } + + /** + * Initialize the right controller by the user's role + * + * @param string $controller + * @return mixed + */ + private function getController(string $controller) { + $namespace = "\\App\\Controller\\"; + $controller = $namespace . $controller; + return new $controller(); + } + + /** + * Redirect the return by the response's type + * + * @param HttpResponse $response + * @return void + */ + private function handleResponseByType(HttpResponse $response): void { + http_response_code($response->getCode()); + if ($response instanceof ViewHttpResponse) { + $this->displayViewByKind($response); + } elseif ($response instanceof JsonHttpResponse) { + header('Content-type: application/json'); + echo $response->getJson(); + } + } + + /** + * Use the right method to display the response + * + * @param ViewHttpResponse $response + * @return void + */ + private function displayViewByKind(ViewHttpResponse $response): void { + $file = $response->getFile(); + $args = $response->getArguments(); + + switch ($response->getViewKind()) { + case ViewHttpResponse::REACT_VIEW: + send_react_front($file, $args); + break; + case ViewHttpResponse::TWIG_VIEW: + try { + $loader = new FilesystemLoader('../src/Views/'); + $twig = new Environment($loader); + $twig->display($file, $args); + } catch (RuntimeError|SyntaxError|LoaderError $e) { + http_response_code(500); + echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); + throw $e; + } + break; + } + } +} diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..930cd67 --- /dev/null +++ b/src/Controller/UserController.php @@ -0,0 +1,16 @@ + + + + + + + Document + + +

Page Home à faire

+ + \ No newline at end of file From 27c7c89b0b71f1151c8a125fdb534a208a7792e4 Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Tue, 21 Nov 2023 22:43:33 +0100 Subject: [PATCH 11/38] WIP HomePage --- src/Views/home.twig | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Views/home.twig b/src/Views/home.twig index b65af07..600ed1d 100644 --- a/src/Views/home.twig +++ b/src/Views/home.twig @@ -9,5 +9,31 @@

Page Home à faire

+ +

Mes équipes

+ +{% for team in recentTeam %} +
+

{{team.name}}

+
+{% endfor %} + +

Mes strategies

+ + + +{% for tactic in recentTactic %} +
+

{{tactic.id}} - {{tactic.name}} - {{tactic.creation_date}}

+
+{% endfor %} + +

+ + + + + + \ No newline at end of file From 982acf5e09bba4aa8ae91965c55ce1fa534ee7ea Mon Sep 17 00:00:00 2001 From: Override-6 Date: Wed, 22 Nov 2023 01:58:50 +0100 Subject: [PATCH 12/38] integrate sessions for tactics --- sql/.guard | 0 sql/setup-tables.sql | 22 ++++--- src/Controller/FrontController.php | 66 ++++++++++++++------- src/Controller/Sub/AuthController.php | 11 +++- src/Controller/Sub/EditorController.php | 18 ++++-- src/Controller/Sub/VisualizerController.php | 11 +++- src/Controller/UserController.php | 33 +++-------- src/Controller/VisitorController.php | 30 ++++++++++ src/Data/Account.php | 17 ++++-- src/Data/TacticInfo.php | 13 +++- src/Gateway/AccountGateway.php | 15 ++--- src/Gateway/FormResultGateway.php | 35 ----------- src/Gateway/TacticInfoGateway.php | 15 +++-- src/Http/HttpCodes.php | 4 ++ src/Http/HttpResponse.php | 25 +++++++- src/Http/JsonHttpResponse.php | 2 +- src/Http/ViewHttpResponse.php | 2 +- src/Model/AuthModel.php | 13 ++-- src/Model/TacticModel.php | 8 +-- src/Session/MutableSessionHandle.php | 1 + src/Validation/ValidationFail.php | 2 +- src/Validator/TacticValidator.php | 22 +++++++ src/Views/error.html.twig | 2 +- 23 files changed, 231 insertions(+), 136 deletions(-) delete mode 100644 sql/.guard create mode 100644 src/Controller/VisitorController.php delete mode 100644 src/Gateway/FormResultGateway.php create mode 100644 src/Validator/TacticValidator.php diff --git a/sql/.guard b/sql/.guard deleted file mode 100644 index e69de29..0000000 diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index a289336..457803c 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -5,20 +5,24 @@ DROP TABLE IF EXISTS TacticInfo; CREATE TABLE FormEntries ( - name varchar, - description varchar + name varchar NOT NULL, + description varchar NOT NULL ); CREATE TABLE Account ( - username varchar, - hash varchar, - email varchar unique, - token varchar(256) NOT NULL UNIQUE + id integer PRIMARY KEY AUTOINCREMENT, + email varchar UNIQUE NOT NULL, + username varchar NOT NULL, + token varchar UNIQUE NOT NULL, + hash varchar NOT NULL ); CREATE TABLE TacticInfo ( id integer PRIMARY KEY AUTOINCREMENT, - name varchar, - creation_date timestamp DEFAULT CURRENT_TIMESTAMP -); \ No newline at end of file + name varchar NOT NULL, + creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, + owner integer NOT NULL, + FOREIGN KEY (owner) REFERENCES Account +); + diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index d5c6417..3d73d80 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -8,7 +8,7 @@ use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; use App\Session\MutableSessionHandle; -use Exception; +use App\Validation\ValidationFail; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; @@ -17,23 +17,33 @@ use Twig\Loader\FilesystemLoader; class FrontController { private AltoRouter $router; + private string $basePath; + + private const USER_CONTROLLER = "UserController"; + private const VISITOR_CONTROLLER = "VisitorController"; + public function __construct(string $basePath) { $this->router = $this->createRouter($basePath); $this->initializeRouterMap(); + $this->basePath = $basePath; } /** - * Main behavior of the FrontController - * + * @param MutableSessionHandle $session * @return void + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError */ public function run(MutableSessionHandle $session): void { $match = $this->router->match(); - if ($match != null) { + if ($match) { $this->handleMatch($match, $session); } else { - $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND)); + $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [ + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")] + ], HttpCodes::NOT_FOUND)); } } @@ -55,13 +65,14 @@ class FrontController { * @return void */ private function initializeRouterMap(): void { - $this->router->map("GET", "/", "UserController"); - $this->router->map("GET|POST", "/[a:action]?", "UserController"); - $this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); + $this->router->map("GET", "/home", self::USER_CONTROLLER); + $this->router->map("GET|POST", "/user/[a:action]/[i:idTactic]?", self::USER_CONTROLLER); + $this->router->map("GET|POST", "/visitor/[a:action]", self::VISITOR_CONTROLLER); } /** * @param array $match + * @param MutableSessionHandle $session * @return void */ private function handleMatch(array $match, MutableSessionHandle $session): void { @@ -73,26 +84,32 @@ class FrontController { $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params), $session)); } + /** - * @param string $controller + * @param string $controllerName * @param string $action * @param array $params * @param MutableSessionHandle $session * @return HttpResponse */ - - private function tryToCall(string $controller, string $action, array $params, MutableSessionHandle $session): HttpResponse { - $controller = $this->getController($controller); - try { - if (is_callable([$controller, $action])) { - // append the session as the last parameter of a controller function - $params[] = $session; - return call_user_func_array([$controller, $action], $params); - } else { - return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + private function tryToCall(string $controllerName, string $action, array $params, MutableSessionHandle $session): HttpResponse { + if ($controllerName != self::VISITOR_CONTROLLER) { + $account = $session->getAccount(); + if ($account == null) { + return HttpResponse::redirect($this->basePath . "/visitor/login"); } - } catch (Exception $e) { - return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + + $controller = $this->getController($controllerName); + + if (is_callable([$controller, $action])) { + // append the session as the last parameter of a controller function + $params[] = $session; + return call_user_func_array([$controller, $action], $params); + } else { + return ViewHttpResponse::twig("error.html.twig", [ + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")] + ], HttpCodes::NOT_FOUND); } } @@ -106,7 +123,7 @@ class FrontController { if (isset($match["params"]["action"])) { return $match["params"]["action"]; } - return "default"; + return "home"; } /** @@ -129,6 +146,11 @@ class FrontController { */ private function handleResponseByType(HttpResponse $response): void { http_response_code($response->getCode()); + + foreach ($response->getHeaders() as $header => $value) { + header("$header: $value"); + } + if ($response instanceof ViewHttpResponse) { $this->displayViewByKind($response); } elseif ($response instanceof JsonHttpResponse) { diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/Sub/AuthController.php index 7a537f2..fc72f67 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -7,6 +7,7 @@ use App\Http\HttpRequest; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; use App\Model\AuthModel; +use App\Session\MutableSessionHandle; use App\Validation\FieldValidationFail; use App\Validation\ValidationFail; use App\Validation\Validators; @@ -42,9 +43,10 @@ class AuthController { /** * @param mixed[] $request + * @param MutableSessionHandle $session * @return HttpResponse */ - public function confirmRegister(array $request): HttpResponse { + public function confirmRegister(array $request, MutableSessionHandle $session): HttpResponse { $fails = []; $request = HttpRequest::from($request, $fails, [ "username" => [Validators::name(), Validators::lenBetween(2, 32)], @@ -59,6 +61,9 @@ class AuthController { if (!empty($fails)) { return $this->displayBadFields("display_register.html.twig", $fails); } + + $session->setAccount($account); + return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); } @@ -71,7 +76,7 @@ class AuthController { * @param mixed[] $request * @return HttpResponse */ - public function confirmLogin(array $request): HttpResponse { + public function confirmLogin(array $request, MutableSessionHandle $session): HttpResponse { $fails = []; $request = HttpRequest::from($request, $fails, [ "password" => [Validators::lenBetween(6, 256)], @@ -86,6 +91,8 @@ class AuthController { return $this->displayBadFields("display_login.html.twig", $fails); } + $session->setAccount($account); + return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); } diff --git a/src/Controller/Sub/EditorController.php b/src/Controller/Sub/EditorController.php index ce7a0a3..1188bc9 100644 --- a/src/Controller/Sub/EditorController.php +++ b/src/Controller/Sub/EditorController.php @@ -3,6 +3,7 @@ namespace App\Controller\Sub; use App\Connexion; +use App\Controller\VisitorController; use App\Data\TacticInfo; use App\Gateway\TacticInfoGateway; use App\Http\HttpCodes; @@ -10,6 +11,9 @@ use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; use App\Model\TacticModel; +use App\Session\SessionHandle; +use App\Validation\ValidationFail; +use App\Validator\TacticValidator; class EditorController { private TacticModel $model; @@ -22,24 +26,28 @@ class EditorController { return ViewHttpResponse::react("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]); } - public function createNew(): HttpResponse { - $tactic = $this->model->makeNewDefault(); + public function createNew(SessionHandle $session): HttpResponse { + $tactic = $this->model->makeNewDefault($session->getAccount()->getId()); return $this->openEditor($tactic); } /** * returns an editor view for a given tactic * @param int $id the targeted tactic identifier + * @param SessionHandle $session * @return HttpResponse */ - public function edit(int $id): HttpResponse { + public function edit(int $id, SessionHandle $session): HttpResponse { $tactic = $this->model->get($id); - if ($tactic == null) { - return new JsonHttpResponse("la tactique " . $id . " n'existe pas", HttpCodes::NOT_FOUND); + $failure = TacticValidator::validateAccess($tactic, $session->getAccount()->getId()); + + if ($failure != null) { + return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); } return $this->openEditor($tactic); } + } diff --git a/src/Controller/Sub/VisualizerController.php b/src/Controller/Sub/VisualizerController.php index e3b5663..f70b18e 100644 --- a/src/Controller/Sub/VisualizerController.php +++ b/src/Controller/Sub/VisualizerController.php @@ -7,6 +7,9 @@ use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; use App\Model\TacticModel; +use App\Session\SessionHandle; +use App\Validation\ValidationFail; +use App\Validator\TacticValidator; class VisualizerController { private TacticModel $tacticModel; @@ -19,11 +22,13 @@ class VisualizerController { $this->tacticModel = $tacticModel; } - public function visualize(int $id): HttpResponse { + public function visualize(int $id, SessionHandle $session): HttpResponse { $tactic = $this->tacticModel->get($id); - if ($tactic == null) { - return new JsonHttpResponse("la tactique " . $id . " n'existe pas", HttpCodes::NOT_FOUND); + $failure = TacticValidator::validateAccess($tactic, $session->getAccount()->getId()); + + if ($failure != null) { + return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); } return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]); diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 360c9a3..64a0caa 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -3,46 +3,29 @@ namespace App\Controller; use App\Connexion; -use App\Gateway\AccountGateway; use App\Gateway\TacticInfoGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; -use App\Model\AuthModel; use App\Model\TacticModel; +use App\Session\SessionHandle; -class UserController { +class UserController extends VisitorController { public function home(): HttpResponse { return ViewHttpResponse::twig("home.twig", []); } - public function register(): HttpResponse { - $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - return (new Sub\AuthController($model))->displayRegister(); - } - return (new Sub\AuthController($model))->confirmRegister($_POST); - } - - public function login(): HttpResponse { - $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - return (new Sub\AuthController($model))->displayLogin(); - } - return (new Sub\AuthController($model))->confirmLogin($_POST); - } - - public function open(int $id): HttpResponse { + public function view(int $id, SessionHandle $session): HttpResponse { $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new Sub\VisualizerController($model))->visualize($id); + return (new Sub\VisualizerController($model))->visualize($id, $session); } - public function edit(int $id): HttpResponse { + public function edit(int $id, SessionHandle $session): HttpResponse { $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new Sub\EditorController($model))->edit($id); + return (new Sub\EditorController($model))->edit($id, $session); } - public function create(): HttpResponse { + public function create(SessionHandle $session): HttpResponse { $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new Sub\EditorController($model))->createNew(); + return (new Sub\EditorController($model))->createNew($session); } } diff --git a/src/Controller/VisitorController.php b/src/Controller/VisitorController.php new file mode 100644 index 0000000..56e0ec2 --- /dev/null +++ b/src/Controller/VisitorController.php @@ -0,0 +1,30 @@ +displayRegister(); + } + return (new Sub\AuthController($model))->confirmRegister($_POST, $session); + } + + public final function login(MutableSessionHandle $session): HttpResponse { + $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return (new Sub\AuthController($model))->displayLogin(); + } + return (new Sub\AuthController($model))->confirmLogin($_POST, $session); + } + +} \ No newline at end of file diff --git a/src/Data/Account.php b/src/Data/Account.php index 8b58e1c..0ed4339 100755 --- a/src/Data/Account.php +++ b/src/Data/Account.php @@ -25,18 +25,27 @@ class Account { */ private string $name; + /** + * @var int + */ + private int $id; + /** * @param string $email - * @param string $phoneNumber - * @param AccountUser $user - * @param Team[] $teams + * @param string $name + * @param string $token * @param int $id */ - public function __construct(string $email, string $name, string $token) { + public function __construct(string $email, string $name, string $token, int $id) { $this->email = $email; $this->name = $name; $this->token = $token; + $this->id = $id; + } + + public function getId(): int { + return $this->id; } public function getEmail(): string { diff --git a/src/Data/TacticInfo.php b/src/Data/TacticInfo.php index eef7bb3..a2a576e 100644 --- a/src/Data/TacticInfo.php +++ b/src/Data/TacticInfo.php @@ -7,14 +7,18 @@ class TacticInfo implements \JsonSerializable { private string $name; private int $creation_date; + private int $ownerId; + /** * @param int $id * @param string $name * @param int $creation_date + * @param int $ownerId */ - public function __construct(int $id, string $name, int $creation_date) { + public function __construct(int $id, string $name, int $creation_date, int $ownerId) { $this->id = $id; $this->name = $name; + $this->ownerId = $ownerId; $this->creation_date = $creation_date; } @@ -26,6 +30,13 @@ class TacticInfo implements \JsonSerializable { return $this->name; } + /** + * @return int + */ + public function getOwnerId(): int { + return $this->ownerId; + } + public function getCreationTimestamp(): int { return $this->creation_date; } diff --git a/src/Gateway/AccountGateway.php b/src/Gateway/AccountGateway.php index a4686d6..8320f33 100644 --- a/src/Gateway/AccountGateway.php +++ b/src/Gateway/AccountGateway.php @@ -22,13 +22,14 @@ class AccountGateway { } - public function insertAccount(Account $account, string $hash): void { - $this->con->exec("INSERT INTO Account VALUES (:username,:hash,:email)", [ - ':username' => [$account->getName(), PDO::PARAM_STR], + public function insertAccount(string $name, string $email, string $token, string $hash): int { + $this->con->exec("INSERT INTO Account(username, hash, email, token) VALUES (:username,:hash,:email,:token)", [ + ':username' => [$name, PDO::PARAM_STR], ':hash' => [$hash, PDO::PARAM_STR], - ':email' => [$account->getEmail(), PDO::PARAM_STR], - ':token' => [$account->getToken(), PDO::PARAM_STR] + ':email' => [$email, PDO::PARAM_STR], + ':token' => [$token, PDO::PARAM_STR] ]); + return intval($this->con->lastInsertId()); } public function getHash(string $email): string { @@ -44,12 +45,12 @@ class AccountGateway { * @return Account|null */ public function getAccount(string $email): ?Account { - $results = $this->con->fetch("SELECT username,email,token FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); + $results = $this->con->fetch("SELECT * FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); if (empty($results)) return null; $acc = $results[0]; - return new Account($acc["email"], $acc["name"], $acc["token"]); + return new Account($acc["email"], $acc["username"], $acc["token"], $acc["id"]); } diff --git a/src/Gateway/FormResultGateway.php b/src/Gateway/FormResultGateway.php deleted file mode 100644 index 36178ad..0000000 --- a/src/Gateway/FormResultGateway.php +++ /dev/null @@ -1,35 +0,0 @@ -con = $con; - } - - - public function insert(string $username, string $description): void { - $this->con->exec( - "INSERT INTO FormEntries VALUES (:name, :description)", - [ - ":name" => [$username, PDO::PARAM_STR], - "description" => [$description, PDO::PARAM_STR], - ] - ); - } - - /** - * @return array - */ - public function listResults(): array { - return $this->con->fetch("SELECT * FROM FormEntries", []); - } -} diff --git a/src/Gateway/TacticInfoGateway.php b/src/Gateway/TacticInfoGateway.php index 3441c9a..6aa2cca 100644 --- a/src/Gateway/TacticInfoGateway.php +++ b/src/Gateway/TacticInfoGateway.php @@ -28,19 +28,22 @@ class TacticInfoGateway { $row = $res[0]; - return new TacticInfo($id, $row["name"], strtotime($row["creation_date"])); + return new TacticInfo($id, $row["name"], strtotime($row["creation_date"]), $row["owner"]); } - public function insert(string $name): TacticInfo { + public function insert(string $name, int $owner): TacticInfo { $this->con->exec( - "INSERT INTO TacticInfo(name) VALUES(:name)", - [":name" => [$name, PDO::PARAM_STR]] + "INSERT INTO TacticInfo(name, owner) VALUES(:name, :owner)", + [ + ":name" => [$name, PDO::PARAM_STR], + ":owner" => [$owner, PDO::PARAM_INT] + ] ); $row = $this->con->fetch( - "SELECT id, creation_date FROM TacticInfo WHERE :id = id", + "SELECT id, creation_date, owner FROM TacticInfo WHERE :id = id", [':id' => [$this->con->lastInsertId(), PDO::PARAM_INT]] )[0]; - return new TacticInfo(intval($row["id"]), $name, strtotime($row["creation_date"])); + return new TacticInfo(intval($row["id"]), $name, strtotime($row["creation_date"]), $row["owner"]); } public function updateName(int $id, string $name): void { diff --git a/src/Http/HttpCodes.php b/src/Http/HttpCodes.php index f9d550c..c2b01df 100644 --- a/src/Http/HttpCodes.php +++ b/src/Http/HttpCodes.php @@ -7,7 +7,11 @@ namespace App\Http; */ class HttpCodes { public const OK = 200; + public const FOUND = 302; public const BAD_REQUEST = 400; + + public const FORBIDDEN = 403; + public const NOT_FOUND = 404; } diff --git a/src/Http/HttpResponse.php b/src/Http/HttpResponse.php index 5d8c3bf..d6d6a9a 100644 --- a/src/Http/HttpResponse.php +++ b/src/Http/HttpResponse.php @@ -3,21 +3,42 @@ namespace App\Http; class HttpResponse { + + /** + * @var array + */ + private array $headers; private int $code; /** * @param int $code + * @param array $headers */ - public function __construct(int $code) { + public function __construct(int $code, array $headers) { $this->code = $code; + $this->headers = $headers; } public function getCode(): int { return $this->code; } + /** + * @return array + */ + public function getHeaders(): array { + return $this->headers; + } + public static function fromCode(int $code): HttpResponse { - return new HttpResponse($code); + return new HttpResponse($code, []); + } + + public static function redirect(string $url, int $code = HttpCodes::FOUND): HttpResponse { + if ($code < 300 || $code >= 400) { + throw new \InvalidArgumentException("given code is not a redirection http code"); + } + return new HttpResponse($code, ["Location" => $url]); } } diff --git a/src/Http/JsonHttpResponse.php b/src/Http/JsonHttpResponse.php index bbd3d80..a01ffd9 100644 --- a/src/Http/JsonHttpResponse.php +++ b/src/Http/JsonHttpResponse.php @@ -12,7 +12,7 @@ class JsonHttpResponse extends HttpResponse { * @param mixed $payload */ public function __construct($payload, int $code = HttpCodes::OK) { - parent::__construct($code); + parent::__construct($code, []); $this->payload = $payload; } diff --git a/src/Http/ViewHttpResponse.php b/src/Http/ViewHttpResponse.php index 2e517d7..9db80c5 100644 --- a/src/Http/ViewHttpResponse.php +++ b/src/Http/ViewHttpResponse.php @@ -26,7 +26,7 @@ class ViewHttpResponse extends HttpResponse { * @param array $arguments */ private function __construct(int $kind, string $file, array $arguments, int $code = HttpCodes::OK) { - parent::__construct($code); + parent::__construct($code, []); $this->kind = $kind; $this->file = $file; $this->arguments = $arguments; diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index b786c2e..a12ce9e 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -2,7 +2,6 @@ namespace App\Model; -use App\Controller\AuthController; use App\Data\Account; use App\Gateway\AccountGateway; use App\Validation\FieldValidationFail; @@ -37,15 +36,15 @@ class AuthModel { $failures[] = new FieldValidationFail("email", "email already exist"); } - if (!empty($errors)) { + if (!empty($failures)) { return null; } $hash = password_hash($password, PASSWORD_DEFAULT); - $account = new Account($email, $username, $this->generateToken()); - $this->gateway->insertAccount($account, $hash); - return $account; + $token = $this->generateToken(); + $accountId = $this->gateway->insertAccount($username, $email, $token, $hash); + return new Account($email, $username, $token, $accountId); } private function generateToken(): string { @@ -60,13 +59,13 @@ class AuthModel { */ public function login(string $email, string $password, array &$failures): ?Account { if (!$this->gateway->exists($email)) { - $failures = new FieldValidationFail("email", "email doesnt exists"); + $failures[] = new FieldValidationFail("email", "email doesnt exists"); return null; } $hash = $this->gateway->getHash($email); if (!password_verify($password, $hash)) { - $failures = new FieldValidationFail("password", "invalid password"); + $failures[] = new FieldValidationFail("password", "invalid password"); return null; } diff --git a/src/Model/TacticModel.php b/src/Model/TacticModel.php index cabcdec..73cfd91 100644 --- a/src/Model/TacticModel.php +++ b/src/Model/TacticModel.php @@ -18,12 +18,12 @@ class TacticModel { $this->tactics = $tactics; } - public function makeNew(string $name): TacticInfo { - return $this->tactics->insert($name); + public function makeNew(string $name, int $ownerId): TacticInfo { + return $this->tactics->insert($name, $ownerId); } - public function makeNewDefault(): ?TacticInfo { - return $this->tactics->insert(self::TACTIC_DEFAULT_NAME); + public function makeNewDefault(int $ownerId): ?TacticInfo { + return $this->tactics->insert(self::TACTIC_DEFAULT_NAME, $ownerId); } /** diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php index a7822a3..e142049 100644 --- a/src/Session/MutableSessionHandle.php +++ b/src/Session/MutableSessionHandle.php @@ -5,5 +5,6 @@ namespace App\Session; use App\Data\Account; interface MutableSessionHandle extends SessionHandle { + public function setAccount(Account $account): void; } \ No newline at end of file diff --git a/src/Validation/ValidationFail.php b/src/Validation/ValidationFail.php index 4f1ec22..83d6c76 100644 --- a/src/Validation/ValidationFail.php +++ b/src/Validation/ValidationFail.php @@ -34,7 +34,7 @@ class ValidationFail implements JsonSerializable { } public static function notFound(string $message): ValidationFail { - return new ValidationFail("not found", $message); + return new ValidationFail("Not found", $message); } } diff --git a/src/Validator/TacticValidator.php b/src/Validator/TacticValidator.php new file mode 100644 index 0000000..711fb72 --- /dev/null +++ b/src/Validator/TacticValidator.php @@ -0,0 +1,22 @@ +getId() . " n'existe pas"); + } + + if ($tactic->getOwnerId() != $ownerId) { + return new ValidationFail("Unauthorized", "Vous ne pouvez pas accéder à cette tactique.",); + } + + return null; + } + +} \ No newline at end of file diff --git a/src/Views/error.html.twig b/src/Views/error.html.twig index 1d1db7d..e30c2fa 100644 --- a/src/Views/error.html.twig +++ b/src/Views/error.html.twig @@ -51,7 +51,7 @@ {% endfor %} - + \ No newline at end of file From 39329c93257741db65a6f78cfe8b373dbf151523 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Wed, 22 Nov 2023 03:43:03 +0100 Subject: [PATCH 13/38] integrate session in API, use tokens --- phpstan.neon | 2 + public/api/index.php | 126 ++++++++++++++++++-- public/index.php | 4 +- src/Controller/Api/APIAuthController.php | 39 ++++++ src/Controller/Api/APITacticController.php | 22 ++-- src/Controller/FrontController.php | 4 +- src/Controller/Sub/AuthController.php | 1 - src/Controller/Sub/EditorController.php | 2 +- src/Controller/Sub/VisualizerController.php | 2 +- src/Controller/VisitorController.php | 8 +- src/Gateway/AccountGateway.php | 47 +++++--- src/Gateway/TacticInfoGateway.php | 2 +- src/Http/HttpResponse.php | 1 - src/Model/AuthModel.php | 2 +- src/Model/TacticModel.php | 19 ++- src/Session/MutableSessionHandle.php | 3 +- src/Session/PhpSessionHandle.php | 5 +- src/Session/SessionHandle.php | 4 +- src/Validation/ValidationFail.php | 4 + src/Validator/TacticValidator.php | 9 +- 20 files changed, 239 insertions(+), 67 deletions(-) create mode 100644 src/Controller/Api/APIAuthController.php diff --git a/phpstan.neon b/phpstan.neon index bc6c041..715fe84 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,5 +9,7 @@ parameters: - sql/database.php - profiles/dev-config-profile.php - profiles/prod-config-profile.php + - public/api/index.php excludePaths: - src/react-display-file.php + - public/api/index.php diff --git a/public/api/index.php b/public/api/index.php index 3ed5caa..8cc7287 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -6,32 +6,134 @@ require "../../sql/database.php"; require "../utils.php"; use App\Connexion; +use App\Controller\Api\APIAuthController; use App\Controller\Api\APITacticController; +use App\Data\Account; +use App\Gateway\AccountGateway; use App\Gateway\TacticInfoGateway; +use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; +use App\Model\AuthModel; use App\Model\TacticModel; +use App\Session\SessionHandle; +use App\Validation\ValidationFail; -$con = new Connexion(get_database()); +function getTacticController(): APITacticController { + return new APITacticController(new TacticModel(new TacticInfoGateway(new Connexion(get_database())))); +} + +function getAuthController(): APIAuthController { + return new APIAuthController(new AuthModel(new AccountGateway(new Connexion(get_database())))); +} + +class Action { + /** + * @var callable(mixed[]): HttpResponse $action action to call + */ + private $action; + + private bool $isAuthRequired; + + /** + * @param callable(mixed[]): HttpResponse $action + */ + private function __construct(callable $action, bool $isAuthRequired) { + $this->action = $action; + $this->isAuthRequired = $isAuthRequired; + } + + public function isAuthRequired(): bool { + return $this->isAuthRequired; + } + + /** + * @param mixed[] $params + * @param ?Account $account + * @return HttpResponse + */ + public function run(array $params, ?Account $account): HttpResponse { + $params = array_values($params); + if ($this->isAuthRequired) { + if ($account == null) { + throw new Exception("action requires authorization."); + } + $params[] = $account; + } + + return call_user_func_array($this->action, $params); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return Action an action that does not require to have an authorization. + */ + public static function noAuth(callable $action): Action { + return new Action($action, false); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return Action an action that does require to have an authorization. + */ + public static function auth(callable $action): Action { + return new Action($action, true); + } +} + +/** + * @param mixed[] $match + * @return HttpResponse + * @throws Exception + */ +function handleMatch(array $match): HttpResponse { + if (!$match) { + return new JsonHttpResponse([ValidationFail::notFound("not found")]); + } + + $action = $match['target']; + if (!$action instanceof Action) { + throw new Exception("routed action is not an Action object."); + } + + $auth = null; + + if ($action->isAuthRequired()) { + $auth = tryGetAuthAccount(); + if ($auth == null) { + return new JsonHttpResponse([ValidationFail::unauthorized("Missing or invalid 'Authorization' header")]); + } + } + + return $action->run($match['params'], $auth); +} + +function tryGetAuthAccount(): ?Account { + $headers = getallheaders(); + + // If no authorization header is set, try fallback to php session. + if (!isset($headers['Authorization'])) { + $session = SessionHandle::init(); + return $session->getAccount(); + } + + $token = $headers['Authorization']; + $gateway = new AccountGateway(new Connexion(get_database())); + return $gateway->getAccountFromToken($token); +} $router = new AltoRouter(); $router->setBasePath(get_public_path() . "/api"); -$tacticEndpoint = new APITacticController(new TacticModel(new TacticInfoGateway($con))); -$router->map("POST", "/tactic/[i:id]/edit/name", fn(int $id) => $tacticEndpoint->updateName($id)); -$router->map("GET", "/tactic/[i:id]", fn(int $id) => $tacticEndpoint->getTacticInfo($id)); -$router->map("POST", "/tactic/new", fn() => $tacticEndpoint->newTactic()); +$router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); +$router->map("GET", "/tactic/[i:id]", Action::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc))); +$router->map("POST", "/tactic/new", Action::auth(fn(Account $acc) => getTacticController()->newTactic($acc))); +$router->map("POST", "/auth", Action::noAuth(fn() => getAuthController()->authorize())); $match = $router->match(); -if ($match == null) { - echo "404 not found"; - header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); - exit(1); -} - -$response = call_user_func_array($match['target'], $match['params']); +$response = handleMatch($match); http_response_code($response->getCode()); if ($response instanceof JsonHttpResponse) { diff --git a/public/index.php b/public/index.php index ce1df33..7d42305 100644 --- a/public/index.php +++ b/public/index.php @@ -8,9 +8,9 @@ require "utils.php"; require "../src/react-display.php"; use App\Controller\FrontController; -use App\Session\PhpSessionHandle; +use App\Session\SessionHandle; $basePath = get_public_path(); $frontController = new FrontController($basePath); -$frontController->run(PhpSessionHandle::init()); +$frontController->run(SessionHandle::init()); diff --git a/src/Controller/Api/APIAuthController.php b/src/Controller/Api/APIAuthController.php new file mode 100644 index 0000000..d9e4c23 --- /dev/null +++ b/src/Controller/Api/APIAuthController.php @@ -0,0 +1,39 @@ +model = $model; + } + + + public function authorize(): HttpResponse { + return Control::runChecked([ + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], + "password" => [Validators::lenBetween(6, 256)], + ], function (HttpRequest $req) { + $failures = []; + $account = $this->model->login($req["email"], $req["password"], $failures); + + if (!empty($failures)) { + return new JsonHttpResponse($failures); + } + + return new JsonHttpResponse(["authorization" => $account->getToken()]); + }, true); + } + +} diff --git a/src/Controller/Api/APITacticController.php b/src/Controller/Api/APITacticController.php index ec0edc8..c1fcee3 100644 --- a/src/Controller/Api/APITacticController.php +++ b/src/Controller/Api/APITacticController.php @@ -3,6 +3,7 @@ namespace App\Controller\Api; use App\Controller\Control; +use App\Data\Account; use App\Http\HttpCodes; use App\Http\HttpRequest; use App\Http\HttpResponse; @@ -23,26 +24,33 @@ class APITacticController { $this->model = $model; } - public function updateName(int $tactic_id): HttpResponse { + public function updateName(int $tactic_id, Account $account): HttpResponse { return Control::runChecked([ "name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()], - ], function (HttpRequest $request) use ($tactic_id) { - $this->model->updateName($tactic_id, $request["name"]); + ], function (HttpRequest $request) use ($tactic_id, $account) { + + $failures = $this->model->updateName($tactic_id, $request["name"], $account->getId()); + + if (!empty($failures)) { + //TODO find a system to handle Unauthorized error codes more easily from failures. + return new JsonHttpResponse($failures, HttpCodes::BAD_REQUEST); + } + return HttpResponse::fromCode(HttpCodes::OK); }, true); } - public function newTactic(): HttpResponse { + public function newTactic(Account $account): HttpResponse { return Control::runChecked([ "name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()], - ], function (HttpRequest $request) { - $tactic = $this->model->makeNew($request["name"]); + ], function (HttpRequest $request) use ($account) { + $tactic = $this->model->makeNew($request["name"], $account->getId()); $id = $tactic->getId(); return new JsonHttpResponse(["id" => $id]); }, true); } - public function getTacticInfo(int $id): HttpResponse { + public function getTacticInfo(int $id, Account $account): HttpResponse { $tactic_info = $this->model->get($id); if ($tactic_info == null) { diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 3d73d80..d1bbd6d 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -42,7 +42,7 @@ class FrontController { $this->handleMatch($match, $session); } else { $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")] + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], ], HttpCodes::NOT_FOUND)); } } @@ -108,7 +108,7 @@ class FrontController { return call_user_func_array([$controller, $action], $params); } else { return ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")] + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], ], HttpCodes::NOT_FOUND); } } diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/Sub/AuthController.php index fc72f67..fcf12d5 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -2,7 +2,6 @@ namespace App\Controller\Sub; -use App\Gateway\AccountGateway; use App\Http\HttpRequest; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; diff --git a/src/Controller/Sub/EditorController.php b/src/Controller/Sub/EditorController.php index 1188bc9..dff6e8a 100644 --- a/src/Controller/Sub/EditorController.php +++ b/src/Controller/Sub/EditorController.php @@ -40,7 +40,7 @@ class EditorController { public function edit(int $id, SessionHandle $session): HttpResponse { $tactic = $this->model->get($id); - $failure = TacticValidator::validateAccess($tactic, $session->getAccount()->getId()); + $failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId()); if ($failure != null) { return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); diff --git a/src/Controller/Sub/VisualizerController.php b/src/Controller/Sub/VisualizerController.php index f70b18e..c7e5098 100644 --- a/src/Controller/Sub/VisualizerController.php +++ b/src/Controller/Sub/VisualizerController.php @@ -25,7 +25,7 @@ class VisualizerController { public function visualize(int $id, SessionHandle $session): HttpResponse { $tactic = $this->tacticModel->get($id); - $failure = TacticValidator::validateAccess($tactic, $session->getAccount()->getId()); + $failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId()); if ($failure != null) { return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); diff --git a/src/Controller/VisitorController.php b/src/Controller/VisitorController.php index 56e0ec2..e74934a 100644 --- a/src/Controller/VisitorController.php +++ b/src/Controller/VisitorController.php @@ -9,9 +9,7 @@ use App\Model\AuthModel; use App\Session\MutableSessionHandle; class VisitorController { - - - public final function register(MutableSessionHandle $session): HttpResponse { + final public function register(MutableSessionHandle $session): HttpResponse { $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\AuthController($model))->displayRegister(); @@ -19,7 +17,7 @@ class VisitorController { return (new Sub\AuthController($model))->confirmRegister($_POST, $session); } - public final function login(MutableSessionHandle $session): HttpResponse { + final public function login(MutableSessionHandle $session): HttpResponse { $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); if ($_SERVER['REQUEST_METHOD'] === 'GET') { return (new Sub\AuthController($model))->displayLogin(); @@ -27,4 +25,4 @@ class VisitorController { return (new Sub\AuthController($model))->confirmLogin($_POST, $session); } -} \ No newline at end of file +} diff --git a/src/Gateway/AccountGateway.php b/src/Gateway/AccountGateway.php index 8320f33..3a57abb 100644 --- a/src/Gateway/AccountGateway.php +++ b/src/Gateway/AccountGateway.php @@ -17,39 +17,56 @@ class AccountGateway { } - public function exists(string $email): bool { - return $this->getAccount($email) != null; - } - - public function insertAccount(string $name, string $email, string $token, string $hash): int { $this->con->exec("INSERT INTO Account(username, hash, email, token) VALUES (:username,:hash,:email,:token)", [ ':username' => [$name, PDO::PARAM_STR], ':hash' => [$hash, PDO::PARAM_STR], ':email' => [$email, PDO::PARAM_STR], - ':token' => [$token, PDO::PARAM_STR] + ':token' => [$token, PDO::PARAM_STR], ]); return intval($this->con->lastInsertId()); } - public function getHash(string $email): string { - $results = $this->con->fetch("SELECT hash FROM Account WHERE email = :email", [ - ':email' => [$email, PDO::PARAM_STR] - ]); - return $results[0]['hash']; + /** + * @param string $email + * @return array|null + */ + private function getRowsFromMail(string $email): ?array { + return $this->con->fetch("SELECT * FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]])[0] ?? null; + } + + + public function getHash(string $email): ?string { + $results = $this->getRowsFromMail($email); + if ($results == null) { + return null; + } + return $results['hash']; } + public function exists(string $email): bool { + return $this->getRowsFromMail($email) != null; + } /** * @param string $email * @return Account|null */ - public function getAccount(string $email): ?Account { - $results = $this->con->fetch("SELECT * FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); - if (empty($results)) + public function getAccountFromMail(string $email): ?Account { + $acc = $this->getRowsFromMail($email); + if (empty($acc)) { + return null; + } + + return new Account($email, $acc["username"], $acc["token"], $acc["id"]); + } + + public function getAccountFromToken(string $token): ?Account { + $acc = $this->con->fetch("SELECT * FROM Account WHERE token = :token", [':token' => [$token, PDO::PARAM_STR]])[0] ?? null; + if (empty($acc)) { return null; + } - $acc = $results[0]; return new Account($acc["email"], $acc["username"], $acc["token"], $acc["id"]); } diff --git a/src/Gateway/TacticInfoGateway.php b/src/Gateway/TacticInfoGateway.php index 6aa2cca..efa0a7c 100644 --- a/src/Gateway/TacticInfoGateway.php +++ b/src/Gateway/TacticInfoGateway.php @@ -36,7 +36,7 @@ class TacticInfoGateway { "INSERT INTO TacticInfo(name, owner) VALUES(:name, :owner)", [ ":name" => [$name, PDO::PARAM_STR], - ":owner" => [$owner, PDO::PARAM_INT] + ":owner" => [$owner, PDO::PARAM_INT], ] ); $row = $this->con->fetch( diff --git a/src/Http/HttpResponse.php b/src/Http/HttpResponse.php index d6d6a9a..e5db3e8 100644 --- a/src/Http/HttpResponse.php +++ b/src/Http/HttpResponse.php @@ -3,7 +3,6 @@ namespace App\Http; class HttpResponse { - /** * @var array */ diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index a12ce9e..1739ab5 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -69,7 +69,7 @@ class AuthModel { return null; } - return $this->gateway->getAccount($email); + return $this->gateway->getAccountFromMail($email); } diff --git a/src/Model/TacticModel.php b/src/Model/TacticModel.php index 73cfd91..8111963 100644 --- a/src/Model/TacticModel.php +++ b/src/Model/TacticModel.php @@ -2,8 +2,10 @@ namespace App\Model; +use App\Data\Account; use App\Data\TacticInfo; use App\Gateway\TacticInfoGateway; +use App\Validation\ValidationFail; class TacticModel { public const TACTIC_DEFAULT_NAME = "Nouvelle tactique"; @@ -39,15 +41,22 @@ class TacticModel { * Update the name of a tactic * @param int $id the tactic identifier * @param string $name the new name to set - * @return bool true if the update was done successfully + * @return ValidationFail[] failures, if any */ - public function updateName(int $id, string $name): bool { - if ($this->tactics->get($id) == null) { - return false; + public function updateName(int $id, string $name, int $authId): array { + + $tactic = $this->tactics->get($id); + + if ($tactic == null) { + return [ValidationFail::notFound("Could not find tactic")]; + } + + if ($tactic->getOwnerId() != $authId) { + return [ValidationFail::unauthorized()]; } $this->tactics->updateName($id, $name); - return true; + return []; } } diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php index e142049..2bcb0fc 100644 --- a/src/Session/MutableSessionHandle.php +++ b/src/Session/MutableSessionHandle.php @@ -5,6 +5,5 @@ namespace App\Session; use App\Data\Account; interface MutableSessionHandle extends SessionHandle { - public function setAccount(Account $account): void; -} \ No newline at end of file +} diff --git a/src/Session/PhpSessionHandle.php b/src/Session/PhpSessionHandle.php index ea0e7c2..09d862c 100644 --- a/src/Session/PhpSessionHandle.php +++ b/src/Session/PhpSessionHandle.php @@ -5,8 +5,7 @@ namespace App\Session; use App\Data\Account; class PhpSessionHandle implements MutableSessionHandle { - - public static function init(): PhpSessionHandle { + public static function init(): SessionHandle { if (session_status() !== PHP_SESSION_NONE) { throw new \Exception("A php session is already started !"); } @@ -21,4 +20,4 @@ class PhpSessionHandle implements MutableSessionHandle { public function setAccount(Account $account): void { $_SESSION["account"] = $account; } -} \ No newline at end of file +} diff --git a/src/Session/SessionHandle.php b/src/Session/SessionHandle.php index 5a65794..6ffb9c0 100644 --- a/src/Session/SessionHandle.php +++ b/src/Session/SessionHandle.php @@ -5,7 +5,5 @@ namespace App\Session; use App\Data\Account; interface SessionHandle { - public function getAccount(): ?Account; - -} \ No newline at end of file +} diff --git a/src/Validation/ValidationFail.php b/src/Validation/ValidationFail.php index 83d6c76..2229a53 100644 --- a/src/Validation/ValidationFail.php +++ b/src/Validation/ValidationFail.php @@ -37,4 +37,8 @@ class ValidationFail implements JsonSerializable { return new ValidationFail("Not found", $message); } + public static function unauthorized(string $message = "Unauthorized"): ValidationFail { + return new ValidationFail("Unauthorized", $message); + } + } diff --git a/src/Validator/TacticValidator.php b/src/Validator/TacticValidator.php index 711fb72..e39cd51 100644 --- a/src/Validator/TacticValidator.php +++ b/src/Validator/TacticValidator.php @@ -6,17 +6,16 @@ use App\Data\TacticInfo; use App\Validation\ValidationFail; class TacticValidator { - - public static function validateAccess(?TacticInfo $tactic, int $ownerId): ?ValidationFail { + public static function validateAccess(?TacticInfo $tactic, int $tacticId, int $ownerId): ?ValidationFail { if ($tactic == null) { - return ValidationFail::notFound("La tactique " . $tactic->getId() . " n'existe pas"); + return ValidationFail::notFound("La tactique $tacticId n'existe pas"); } if ($tactic->getOwnerId() != $ownerId) { - return new ValidationFail("Unauthorized", "Vous ne pouvez pas accéder à cette tactique.",); + return ValidationFail::unauthorized("Vous ne pouvez pas accéder à cette tactique."); } return null; } -} \ No newline at end of file +} From 69be9399549b2855bb01c715de63fa685c78c622 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 22 Nov 2023 09:38:35 +0100 Subject: [PATCH 14/38] add target url in session to bring back user to its initial target after auth --- public/api/index.php | 3 ++- public/index.php | 4 ++-- src/Controller/FrontController.php | 11 +++++++---- src/Controller/Sub/AuthController.php | 7 +++++-- src/Session/MutableSessionHandle.php | 3 +++ src/Session/PhpSessionHandle.php | 8 ++++++++ src/Session/SessionHandle.php | 2 ++ 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/public/api/index.php b/public/api/index.php index 8cc7287..580f794 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -16,6 +16,7 @@ use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; use App\Model\AuthModel; use App\Model\TacticModel; +use App\Session\PhpSessionHandle; use App\Session\SessionHandle; use App\Validation\ValidationFail; @@ -113,7 +114,7 @@ function tryGetAuthAccount(): ?Account { // If no authorization header is set, try fallback to php session. if (!isset($headers['Authorization'])) { - $session = SessionHandle::init(); + $session = PhpSessionHandle::init(); return $session->getAccount(); } diff --git a/public/index.php b/public/index.php index 7d42305..ce1df33 100644 --- a/public/index.php +++ b/public/index.php @@ -8,9 +8,9 @@ require "utils.php"; require "../src/react-display.php"; use App\Controller\FrontController; -use App\Session\SessionHandle; +use App\Session\PhpSessionHandle; $basePath = get_public_path(); $frontController = new FrontController($basePath); -$frontController->run(SessionHandle::init()); +$frontController->run(PhpSessionHandle::init()); diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index d1bbd6d..4ac3047 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -40,11 +40,12 @@ class FrontController { $match = $this->router->match(); if ($match) { $this->handleMatch($match, $session); - } else { - $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], - ], HttpCodes::NOT_FOUND)); + return; } + + $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [ + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], + ], HttpCodes::NOT_FOUND)); } /** @@ -96,6 +97,8 @@ class FrontController { if ($controllerName != self::VISITOR_CONTROLLER) { $account = $session->getAccount(); if ($account == null) { + // put in the session the initial url the user wanted to get + $session->setInitialTarget($_SERVER['REQUEST_URI']); return HttpResponse::redirect($this->basePath . "/visitor/login"); } } diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/Sub/AuthController.php index fcf12d5..d94da43 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -63,7 +63,8 @@ class AuthController { $session->setAccount($account); - return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); + $target_url = $session->getInitialTarget(); + return HttpResponse::redirect($target_url ?? "/home"); } @@ -92,7 +93,9 @@ class AuthController { $session->setAccount($account); - return ViewHttpResponse::twig("display_auth_confirm.html.twig", ['username' => $account->getName(), 'email' => $account->getEmail()]); + $target_url = $session->getInitialTarget(); + $session->setInitialTarget(null); + return HttpResponse::redirect($target_url ?? "/home"); } } diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php index 2bcb0fc..64ee52f 100644 --- a/src/Session/MutableSessionHandle.php +++ b/src/Session/MutableSessionHandle.php @@ -5,5 +5,8 @@ namespace App\Session; use App\Data\Account; interface MutableSessionHandle extends SessionHandle { + + public function setInitialTarget(?string $url): void; + public function setAccount(Account $account): void; } diff --git a/src/Session/PhpSessionHandle.php b/src/Session/PhpSessionHandle.php index 09d862c..5568eb5 100644 --- a/src/Session/PhpSessionHandle.php +++ b/src/Session/PhpSessionHandle.php @@ -17,7 +17,15 @@ class PhpSessionHandle implements MutableSessionHandle { return $_SESSION["account"] ?? null; } + public function getInitialTarget(): ?string { + return $_SESSION["target"] ?? null; + } + public function setAccount(Account $account): void { $_SESSION["account"] = $account; } + + public function setInitialTarget(?string $url): void { + $_SESSION["target"] = $url; + } } diff --git a/src/Session/SessionHandle.php b/src/Session/SessionHandle.php index 6ffb9c0..2114877 100644 --- a/src/Session/SessionHandle.php +++ b/src/Session/SessionHandle.php @@ -5,5 +5,7 @@ namespace App\Session; use App\Data\Account; interface SessionHandle { + public function getInitialTarget(): ?string; + public function getAccount(): ?Account; } From 55c67cc484d71af0418f17da315c8acb1c655831 Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Wed, 22 Nov 2023 09:46:28 +0100 Subject: [PATCH 15/38] Add a basic display of the tactics in the home page --- src/Controller/UserController.php | 18 ++++++++++- src/Gateway/TacticInfoGateway.php | 17 +++++++++++ src/Model/TacticModel.php | 4 +++ src/Views/home.css | 6 ++++ src/Views/home.twig | 51 +++++++++++++++++++++---------- 5 files changed, 79 insertions(+), 17 deletions(-) create mode 100644 src/Views/home.css diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 930cd67..d93a49b 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -2,12 +2,28 @@ namespace App\Controller; +use App\Connexion; +use App\Gateway\AuthGateway; +use App\Gateway\TacticInfoGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; +use App\Model\AuthModel; +use App\Model\TacticModel; class UserController { + private TacticModel $tacticMdl; + private AuthModel $accountMdl; + + public function __construct() + { + $con = new Connexion(get_database()); + $this->tacticMdl = new TacticModel(new TacticInfoGateway($con)); + $this->accountMdl = new AuthModel(new AuthGateway($con)); + } + public function home(): HttpResponse { - return ViewHttpResponse::twig("home.twig", []); + $listTactic = $this->tacticMdl->getLast(5); + return ViewHttpResponse::twig("home.twig", ["recentTactic" => $listTactic]); } public function default(): HttpResponse { diff --git a/src/Gateway/TacticInfoGateway.php b/src/Gateway/TacticInfoGateway.php index 3441c9a..4806a04 100644 --- a/src/Gateway/TacticInfoGateway.php +++ b/src/Gateway/TacticInfoGateway.php @@ -31,6 +31,23 @@ class TacticInfoGateway { return new TacticInfo($id, $row["name"], strtotime($row["creation_date"])); } + /** + * Return the nb last tactics created + * + * @param integer $nb + * @return array|null + */ + public function getLast(int $nb) : ?array { + $res = $this->con->fetch( + "SELECT * FROM TacticInfo ORDER BY creation_date DESC LIMIT :nb ", + [":nb" => [$nb, PDO::PARAM_INT]] + ); + if (count($res) == 0) { + return null; + } + return $res; + } + public function insert(string $name): TacticInfo { $this->con->exec( "INSERT INTO TacticInfo(name) VALUES(:name)", diff --git a/src/Model/TacticModel.php b/src/Model/TacticModel.php index cabcdec..d696b11 100644 --- a/src/Model/TacticModel.php +++ b/src/Model/TacticModel.php @@ -35,6 +35,10 @@ class TacticModel { return $this->tactics->get($id); } + public function getLast(int $nb) : ?array { + return $this->tactics->getLast($nb); + } + /** * Update the name of a tactic * @param int $id the tactic identifier diff --git a/src/Views/home.css b/src/Views/home.css new file mode 100644 index 0000000..c642b09 --- /dev/null +++ b/src/Views/home.css @@ -0,0 +1,6 @@ +.bandeau { + background-color: red; +} +body { + background-color: blue; +} \ No newline at end of file diff --git a/src/Views/home.twig b/src/Views/home.twig index 600ed1d..b7cc7d3 100644 --- a/src/Views/home.twig +++ b/src/Views/home.twig @@ -5,30 +5,49 @@ - Document + Page d'accueil + -

Page Home à faire

+
+

IQ Ball

+

Profil

+
-

Mes équipes

+

Mes équipes

-{% for team in recentTeam %} -
-

{{team.name}}

-
-{% endfor %} -

Mes strategies

+ {% for team in recentTeam %} +
+

{{team.name}}

+
+ {% endfor %} - +

Mes strategies

+ + + + {% if recentTactic != null %} + {% for tactic in recentTactic %} +
+

{{tactic.id}} - {{tactic.name}} - {{tactic.creation_date}}

+ +
+ {% endfor %} + {% else %} +

Aucune tactique créé !

+ {% endif %} -{% for tactic in recentTactic %} -
-

{{tactic.id}} - {{tactic.name}} - {{tactic.creation_date}}

-
-{% endfor %} -

+

From 66410afa2056917a08e32ded7d8ee8b78e2d1bca Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Wed, 22 Nov 2023 09:53:11 +0100 Subject: [PATCH 16/38] Test recentTeam empty in view --- src/Views/home.twig | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Views/home.twig b/src/Views/home.twig index b7cc7d3..500c02a 100644 --- a/src/Views/home.twig +++ b/src/Views/home.twig @@ -22,14 +22,17 @@

Profil

-

Mes équipes

+

Mes équipes

- - {% for team in recentTeam %} -
-

{{team.name}}

-
- {% endfor %} + {% if recentTeam != null %} + {% for team in recentTeam %} +
+

{{team.name}}

+
+ {% endfor %} + {% else %} +

Aucune équipe créé !

+ {% endif %}

Mes strategies

From e44ea4721ef5b9f227269ada097418e4629754e9 Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Wed, 22 Nov 2023 13:03:12 +0100 Subject: [PATCH 17/38] Welcome Page done --- public/img/welcomePage/account.png | Bin 0 -> 507 bytes public/img/welcomePage/account.svg | 1 + src/Views/home.css | 2 +- src/Views/home.twig | 49 +++++++++++++++++++++++------ 4 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 public/img/welcomePage/account.png create mode 100644 public/img/welcomePage/account.svg diff --git a/public/img/welcomePage/account.png b/public/img/welcomePage/account.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed3299083bc52c2f0fa7994ee58a8bc4cb80872 GIT binary patch literal 507 zcmV25Zg zH(#4B7cD{=|_c`Ze4&X!=@5HN#C|jLhiPtH;F@Sq1U)Y5Y zec9tJ#vni|@uhg*Wat3YqZB|i&IR^40)$AR2LtW&hXADI#ZC|PCD+i78Q~lv9e{t^ zp8CAm`qa+Lw?+^F9G&pm0N@cIJ2v@7-<5b6f!Cr- zUj+EE%c;&v)qs>?a7aBnr2yoeqQ@>G9o|I|WE5a5MF}TIMWpRh+(nlva>?TwL&z4H zs?(+vv+HB6ku&TnD=fy*X^i8rL(V%&wy9tAhlcWhh8^ia|0_7M^^)VL@HCVTJJ-*S z3Y6loBMsjBZeaLDhZI7S!_F^95W0&!BZRa;qywWA)!)LFaTKS1I2fiTIU?r(ty4-R xzf9@^Oy1~O&WHv5`c*S^2f6S`~nTSY#9m*AXNYW002ovPDHLkV1k67-3$N# literal 0 HcmV?d00001 diff --git a/public/img/welcomePage/account.svg b/public/img/welcomePage/account.svg new file mode 100644 index 0000000..ce59194 --- /dev/null +++ b/public/img/welcomePage/account.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Views/home.css b/src/Views/home.css index c642b09..1f4f995 100644 --- a/src/Views/home.css +++ b/src/Views/home.css @@ -2,5 +2,5 @@ background-color: red; } body { - background-color: blue; + } \ No newline at end of file diff --git a/src/Views/home.twig b/src/Views/home.twig index 500c02a..1c14e02 100644 --- a/src/Views/home.twig +++ b/src/Views/home.twig @@ -7,19 +7,56 @@ Page d'accueil

IQ Ball

-

Profil

+
+ Account logo +

Mon profil

+

Mes équipes

@@ -49,13 +86,5 @@

Aucune tactique créé !

{% endif %} - -

- - - - - - \ No newline at end of file From b489364cd21b461744bd7cc6196da3113e9c6b20 Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Wed, 22 Nov 2023 13:03:53 +0100 Subject: [PATCH 18/38] delete css file --- src/Views/home.css | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 src/Views/home.css diff --git a/src/Views/home.css b/src/Views/home.css deleted file mode 100644 index 1f4f995..0000000 --- a/src/Views/home.css +++ /dev/null @@ -1,6 +0,0 @@ -.bandeau { - background-color: red; -} -body { - -} \ No newline at end of file From e3875b4f15ce3b710187808420290445fe0b974c Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 22 Nov 2023 13:02:28 +0100 Subject: [PATCH 19/38] add documentation --- Documentation/database_mcd.puml | 17 +++++++++++------ public/api/index.php | 4 +++- sql/setup-tables.sql | 10 ++-------- src/Gateway/TacticInfoGateway.php | 8 ++++---- src/Session/MutableSessionHandle.php | 10 +++++++++- src/Session/PhpSessionHandle.php | 5 ++++- src/Session/SessionHandle.php | 12 ++++++++++++ 7 files changed, 45 insertions(+), 21 deletions(-) diff --git a/Documentation/database_mcd.puml b/Documentation/database_mcd.puml index e698a69..710dfee 100644 --- a/Documentation/database_mcd.puml +++ b/Documentation/database_mcd.puml @@ -2,12 +2,10 @@ object Account { id - name - age email - phoneNumber - passwordHash - profilePicture + username + token + hash } object Team { @@ -26,7 +24,7 @@ object TacticFolder { object Tactic { id_json name - creationDate + creation_date } usecase have_team [ @@ -63,6 +61,10 @@ usecase contains_other_folder [ to contain ] +usecase owns [ + owns +] + Account "0,n" -- have_team have_team -- "1,n" Team @@ -73,6 +75,9 @@ shared_tactic_account -- "0,n" Tactic Tactic "0,n" -- shared_tactic_team shared_tactic_team -- "0,n" Team +Tactic "1,1" -- owns +owns -- Account + Team "0,n" -- shared_folder_team shared_folder_team -- "0,n"TacticFolder diff --git a/public/api/index.php b/public/api/index.php index 580f794..c5f8f2e 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -17,7 +17,6 @@ use App\Http\ViewHttpResponse; use App\Model\AuthModel; use App\Model\TacticModel; use App\Session\PhpSessionHandle; -use App\Session\SessionHandle; use App\Validation\ValidationFail; function getTacticController(): APITacticController { @@ -28,6 +27,9 @@ function getAuthController(): APIAuthController { return new APIAuthController(new AuthModel(new AccountGateway(new Connexion(get_database())))); } +/** + * A Front controller action + */ class Action { /** * @var callable(mixed[]): HttpResponse $action action to call diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index 457803c..26e58da 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -1,13 +1,7 @@ -- drop tables here -DROP TABLE IF EXISTS FormEntries; DROP TABLE IF EXISTS Account; -DROP TABLE IF EXISTS TacticInfo; +DROP TABLE IF EXISTS Tactic; -CREATE TABLE FormEntries -( - name varchar NOT NULL, - description varchar NOT NULL -); CREATE TABLE Account ( id integer PRIMARY KEY AUTOINCREMENT, @@ -17,7 +11,7 @@ CREATE TABLE Account hash varchar NOT NULL ); -CREATE TABLE TacticInfo +CREATE TABLE Tactic ( id integer PRIMARY KEY AUTOINCREMENT, name varchar NOT NULL, diff --git a/src/Gateway/TacticInfoGateway.php b/src/Gateway/TacticInfoGateway.php index efa0a7c..3075f69 100644 --- a/src/Gateway/TacticInfoGateway.php +++ b/src/Gateway/TacticInfoGateway.php @@ -18,7 +18,7 @@ class TacticInfoGateway { public function get(int $id): ?TacticInfo { $res = $this->con->fetch( - "SELECT * FROM TacticInfo WHERE id = :id", + "SELECT * FROM Tactic WHERE id = :id", [":id" => [$id, PDO::PARAM_INT]] ); @@ -33,14 +33,14 @@ class TacticInfoGateway { public function insert(string $name, int $owner): TacticInfo { $this->con->exec( - "INSERT INTO TacticInfo(name, owner) VALUES(:name, :owner)", + "INSERT INTO Tactic(name, owner) VALUES(:name, :owner)", [ ":name" => [$name, PDO::PARAM_STR], ":owner" => [$owner, PDO::PARAM_INT], ] ); $row = $this->con->fetch( - "SELECT id, creation_date, owner FROM TacticInfo WHERE :id = id", + "SELECT id, creation_date, owner FROM Tactic WHERE :id = id", [':id' => [$this->con->lastInsertId(), PDO::PARAM_INT]] )[0]; return new TacticInfo(intval($row["id"]), $name, strtotime($row["creation_date"]), $row["owner"]); @@ -48,7 +48,7 @@ class TacticInfoGateway { public function updateName(int $id, string $name): void { $this->con->exec( - "UPDATE TacticInfo SET name = :name WHERE id = :id", + "UPDATE Tactic SET name = :name WHERE id = :id", [ ":name" => [$name, PDO::PARAM_STR], ":id" => [$id, PDO::PARAM_INT], diff --git a/src/Session/MutableSessionHandle.php b/src/Session/MutableSessionHandle.php index 64ee52f..1459819 100644 --- a/src/Session/MutableSessionHandle.php +++ b/src/Session/MutableSessionHandle.php @@ -4,9 +4,17 @@ namespace App\Session; use App\Data\Account; +/** + * The mutable side of a session handle + */ interface MutableSessionHandle extends SessionHandle { - + /** + * @param string|null $url the url to redirect the user to after authentication. + */ public function setInitialTarget(?string $url): void; + /** + * @param Account $account update the session's account + */ public function setAccount(Account $account): void; } diff --git a/src/Session/PhpSessionHandle.php b/src/Session/PhpSessionHandle.php index 5568eb5..7d6b10b 100644 --- a/src/Session/PhpSessionHandle.php +++ b/src/Session/PhpSessionHandle.php @@ -4,8 +4,11 @@ namespace App\Session; use App\Data\Account; +/** + * A PHP session handle + */ class PhpSessionHandle implements MutableSessionHandle { - public static function init(): SessionHandle { + public static function init(): self { if (session_status() !== PHP_SESSION_NONE) { throw new \Exception("A php session is already started !"); } diff --git a/src/Session/SessionHandle.php b/src/Session/SessionHandle.php index 2114877..663c78c 100644 --- a/src/Session/SessionHandle.php +++ b/src/Session/SessionHandle.php @@ -4,8 +4,20 @@ namespace App\Session; use App\Data\Account; +/** + * An immutable session handle + */ interface SessionHandle { + /** + * The initial target url if the user wanted to perform an action that requires authentication + * but has been required to login first in the application. + * @return string|null Get the initial targeted URL + */ public function getInitialTarget(): ?string; + /** + * The session account if the user is logged in. + * @return Account|null + */ public function getAccount(): ?Account; } From a11dd2e1228299bd0947ce44131be4c21783ed37 Mon Sep 17 00:00:00 2001 From: Yanis DAHMANE-BOUNOUA Date: Tue, 21 Nov 2023 21:30:12 +0100 Subject: [PATCH 20/38] front-controller (#17) I did the FRONT CONTROLLERRRRR. (Salva version lol) When you create a new controller, define the path to call it here (image 1). Don't forget to associate a controller with a role. The main method have to return a `ViewHttpResponse`. If you create a new action, please make sure that the action's main method have the same name than the action (image 2 and 3) and to make the main method public. Co-authored-by: DahmaneYanis Co-authored-by: d_yanis Co-authored-by: Override-6 Reviewed-on: https://codefirst.iut.uca.fr/git/IQBall/Application-Web/pulls/17 Co-authored-by: Yanis DAHMANE-BOUNOUA Co-committed-by: Yanis DAHMANE-BOUNOUA --- Documentation/http.puml | 2 +- Documentation/php.puml | 8 ++ public/index.php | 90 +--------------- src/Controller/EditorController.php | 14 ++- src/Controller/FrontController.php | 162 ++++++++++++++++++++++++++++ src/Controller/UserController.php | 16 +++ src/Views/home.twig | 13 +++ 7 files changed, 210 insertions(+), 95 deletions(-) create mode 100644 Documentation/php.puml create mode 100644 src/Controller/FrontController.php create mode 100644 src/Controller/UserController.php create mode 100644 src/Views/home.twig diff --git a/Documentation/http.puml b/Documentation/http.puml index b41135d..9fbe606 100644 --- a/Documentation/http.puml +++ b/Documentation/http.puml @@ -35,7 +35,7 @@ class ViewHttpResponse extends HttpResponse { - arguments: array - kind: int - + __construct(kind: int, file: string, arguments: array, code: int = HttpCodes::OK) + - __construct(kind: int, file: string, arguments: array, code: int = HttpCodes::OK) + getViewKind(): int + getFile(): string + getArguments(): array diff --git a/Documentation/php.puml b/Documentation/php.puml new file mode 100644 index 0000000..8641900 --- /dev/null +++ b/Documentation/php.puml @@ -0,0 +1,8 @@ +@startuml + +class FrontController { + - router : AltoRouter + +} + +@enduml \ No newline at end of file diff --git a/public/index.php b/public/index.php index 6633860..f898d7f 100644 --- a/public/index.php +++ b/public/index.php @@ -4,92 +4,10 @@ require "../vendor/autoload.php"; require "../config.php"; require "../sql/database.php"; require "utils.php"; +require "../src/react-display.php"; -use App\Connexion; -use App\Controller\EditorController; -use App\Controller\SampleFormController; -use App\Gateway\FormResultGateway; -use App\Gateway\TacticInfoGateway; -use App\Http\JsonHttpResponse; -use App\Http\ViewHttpResponse; -use App\Model\TacticModel; -use Twig\Loader\FilesystemLoader; -use App\Gateway\AuthGateway; -use App\Controller\AuthController; -use App\Validation\ValidationFail; -use App\Controller\ErrorController; -use App\Controller\VisualizerController; - -$loader = new FilesystemLoader('../src/Views/'); -$twig = new \Twig\Environment($loader); +use App\Controller\FrontController; $basePath = get_public_path(); -$con = new Connexion(get_database()); - -// routes initialization -$router = new AltoRouter(); -$router->setBasePath($basePath); - -$sampleFormController = new SampleFormController(new FormResultGateway($con)); -$authGateway = new AuthGateway($con); -$authController = new \App\Controller\AuthController(new \App\Model\AuthModel($authGateway)); -$editorController = new EditorController(new TacticModel(new TacticInfoGateway($con))); -$visualizerController = new VisualizerController(new TacticModel(new TacticInfoGateway($con))); - - -$router->map("GET", "/", fn() => $sampleFormController->displayFormReact()); -$router->map("POST", "/submit", fn() => $sampleFormController->submitFormReact($_POST)); -$router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); -$router->map("POST", "/submit-twig", fn() => $sampleFormController->submitFormTwig($_POST)); -$router->map("GET", "/register", fn() => $authController->displayRegister()); -$router->map("POST", "/register", fn() => $authController->confirmRegister($_POST)); -$router->map("GET", "/login", fn() => $authController->displayLogin()); -$router->map("POST", "/login", fn() => $authController->confirmLogin($_POST)); -$router->map("GET", "/tactic/new", fn() => $editorController->makeNew()); -$router->map("GET", "/tactic/[i:id]/edit", fn(int $id) => $editorController->openEditorFor($id)); -$router->map("GET", "/tactic/[i:id]", fn(int $id) => $visualizerController->openVisualizer($id)); - -$teamController = new \App\Controller\TeamController(new \App\Model\TeamModel(new \App\Gateway\TeamGateway($con))); -$router->map("GET", "/team/new", fn() => $teamController->displaySubmitTeam()); -$router->map("POST", "/team/new", fn() => $teamController->SubmitTeam($_POST)); - -$router->map("GET", "/team/list", fn() => $teamController->displayListTeamByName()); -$router->map("POST", "/team/list", fn() => $teamController->ListTeamByName($_POST)); - -$router->map("GET", "/team/[i:id]", fn(int $id) => $teamController->getTeam($id)); - -$match = $router->match(); - -if ($match == null) { - http_response_code(404); - ErrorController::displayFailures([ValidationFail::notFound("Cette page n'existe pas")], $twig); - return; -} - -$response = call_user_func_array($match['target'], $match['params']); - -http_response_code($response->getCode()); - -if ($response instanceof ViewHttpResponse) { - $file = $response->getFile(); - $args = $response->getArguments(); - - switch ($response->getViewKind()) { - case ViewHttpResponse::REACT_VIEW: - send_react_front($file, $args); - break; - case ViewHttpResponse::TWIG_VIEW: - try { - $twig->display($file, $args); - } catch (\Twig\Error\RuntimeError|\Twig\Error\SyntaxError $e) { - http_response_code(500); - echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); - throw $e; - } - break; - } - -} elseif ($response instanceof JsonHttpResponse) { - header('Content-type: application/json'); - echo $response->getJson(); -} +$frontController = new FrontController($basePath); +$frontController->run(); diff --git a/src/Controller/EditorController.php b/src/Controller/EditorController.php index ed270d1..eb07184 100644 --- a/src/Controller/EditorController.php +++ b/src/Controller/EditorController.php @@ -2,9 +2,10 @@ namespace App\Controller; +use App\Connexion; use App\Data\TacticInfo; +use App\Gateway\TacticInfoGateway; use App\Http\HttpCodes; -use App\Http\HttpRequest; use App\Http\HttpResponse; use App\Http\JsonHttpResponse; use App\Http\ViewHttpResponse; @@ -13,18 +14,15 @@ use App\Model\TacticModel; class EditorController { private TacticModel $model; - /** - * @param TacticModel $model - */ - public function __construct(TacticModel $model) { - $this->model = $model; + public function __construct() { + $this->model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); } private function openEditor(TacticInfo $tactic): HttpResponse { return ViewHttpResponse::react("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]); } - public function makeNew(): HttpResponse { + public function create(): HttpResponse { $tactic = $this->model->makeNewDefault(); return $this->openEditor($tactic); } @@ -34,7 +32,7 @@ class EditorController { * @param int $id the targeted tactic identifier * @return HttpResponse */ - public function openEditorFor(int $id): HttpResponse { + public function edit(int $id): HttpResponse { $tactic = $this->model->get($id); if ($tactic == null) { diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php new file mode 100644 index 0000000..66d5d4f --- /dev/null +++ b/src/Controller/FrontController.php @@ -0,0 +1,162 @@ +router = $this->createRouter($basePath); + $this->initializeRouterMap(); + } + + /** + * Main behavior of the FrontController + * + * @return void + */ + public function run(): void { + $match = $this->router->match(); + if ($match != null) { + $this->handleMatch($match); + } else { + $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND)); + } + } + + /** + * Create a new instance of an AltoRouter + * + * @param string $basePath + * @return AltoRouter + */ + public function createRouter(string $basePath): AltoRouter { + $router = new AltoRouter(); + $router->setBasePath($basePath); + return $router; + } + + /** + * Initialize project's routes + * + * @return void + */ + private function initializeRouterMap(): void { + $this->router->map("GET", "/", "UserController"); + $this->router->map("GET", "/[a:action]?", "UserController"); + $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "EditorController"); + } + + /** + * @param array $match + * @return void + */ + private function handleMatch(array $match): void { + $tag = $match['target']; + + $action = $this->getAction($match); + $params = $match["params"]; + unset($params['action']); + $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params))); + } + + /** + * @param string $controller + * @param string $action + * @param array $params + * @return HttpResponse + */ + private function tryToCall(string $controller, string $action, array $params): HttpResponse { + $controller = $this->getController($controller); + try { + if (is_callable([$controller, $action])) { + return call_user_func_array([$controller, $action], $params); + } else { + return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + } catch (Exception $e) { + return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); + } + } + + /** + * Get the right method to call to do an action + * + * @param array $match + * @return string + */ + private function getAction(array $match): string { + if (isset($match["params"]["action"])) { + return $match["params"]["action"]; + } + return "default"; + } + + /** + * Initialize the right controller by the user's role + * + * @param string $controller + * @return mixed + */ + private function getController(string $controller) { + $namespace = "\\App\\Controller\\"; + $controller = $namespace . $controller; + return new $controller(); + } + + /** + * Redirect the return by the response's type + * + * @param HttpResponse $response + * @return void + */ + private function handleResponseByType(HttpResponse $response): void { + http_response_code($response->getCode()); + if ($response instanceof ViewHttpResponse) { + $this->displayViewByKind($response); + } elseif ($response instanceof JsonHttpResponse) { + header('Content-type: application/json'); + echo $response->getJson(); + } + } + + /** + * Use the right method to display the response + * + * @param ViewHttpResponse $response + * @return void + */ + private function displayViewByKind(ViewHttpResponse $response): void { + $file = $response->getFile(); + $args = $response->getArguments(); + + switch ($response->getViewKind()) { + case ViewHttpResponse::REACT_VIEW: + send_react_front($file, $args); + break; + case ViewHttpResponse::TWIG_VIEW: + try { + $loader = new FilesystemLoader('../src/Views/'); + $twig = new Environment($loader); + $twig->display($file, $args); + } catch (RuntimeError|SyntaxError|LoaderError $e) { + http_response_code(500); + echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); + throw $e; + } + break; + } + } +} diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..930cd67 --- /dev/null +++ b/src/Controller/UserController.php @@ -0,0 +1,16 @@ + + + + + + + Document + + +

Page Home à faire

+ + \ No newline at end of file From 038686e2ed2fcab7fb461bf37cc75ee42f7f2d18 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Tue, 21 Nov 2023 22:57:36 +0100 Subject: [PATCH 21/38] add salva-like routes and controller architecture --- src/Controller/ErrorController.php | 26 -------- src/Controller/FrontController.php | 2 +- src/Controller/SampleFormController.php | 64 ------------------- src/Controller/{ => Sub}/AuthController.php | 4 +- src/Controller/{ => Sub}/EditorController.php | 4 +- .../{ => Sub}/VisualizerController.php | 5 +- src/Controller/UserController.php | 36 ++++++++++- src/Model/AuthModel.php | 1 - 8 files changed, 40 insertions(+), 102 deletions(-) delete mode 100644 src/Controller/ErrorController.php delete mode 100644 src/Controller/SampleFormController.php rename src/Controller/{ => Sub}/AuthController.php (97%) rename src/Controller/{ => Sub}/EditorController.php (93%) rename src/Controller/{ => Sub}/VisualizerController.php (88%) diff --git a/src/Controller/ErrorController.php b/src/Controller/ErrorController.php deleted file mode 100644 index 7fc5239..0000000 --- a/src/Controller/ErrorController.php +++ /dev/null @@ -1,26 +0,0 @@ -display("error.html.twig", ['failures' => $failures]); - } catch (LoaderError|RuntimeError|SyntaxError $e) { - echo "Twig error: $e"; - } - } -} diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 66d5d4f..0600a01 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -56,7 +56,7 @@ class FrontController { private function initializeRouterMap(): void { $this->router->map("GET", "/", "UserController"); $this->router->map("GET", "/[a:action]?", "UserController"); - $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "EditorController"); + $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "UserController"); } /** diff --git a/src/Controller/SampleFormController.php b/src/Controller/SampleFormController.php deleted file mode 100644 index 773a4db..0000000 --- a/src/Controller/SampleFormController.php +++ /dev/null @@ -1,64 +0,0 @@ -gateway = $gateway; - } - - - public function displayFormReact(): HttpResponse { - return ViewHttpResponse::react("views/SampleForm.tsx", []); - } - - public function displayFormTwig(): HttpResponse { - return ViewHttpResponse::twig('sample_form.html.twig', []); - } - - /** - * @param array $form - * @param callable(array>): ViewHttpResponse $response - * @return HttpResponse - */ - private function submitForm(array $form, callable $response): HttpResponse { - return Control::runCheckedFrom($form, [ - "name" => [Validators::lenBetween(0, 32), Validators::name("Le nom ne peut contenir que des lettres, des chiffres et des accents")], - "description" => [Validators::lenBetween(0, 512)], - ], function (HttpRequest $req) use ($response) { - $description = htmlspecialchars($req["description"]); - $this->gateway->insert($req["name"], $description); - $results = ["results" => $this->gateway->listResults()]; - return call_user_func_array($response, [$results]); - }, false); - } - - /** - * @param array $form - * @return HttpResponse - */ - public function submitFormTwig(array $form): HttpResponse { - return $this->submitForm($form, fn(array $results) => ViewHttpResponse::twig('display_results.html.twig', $results)); - } - - /** - * @param array $form - * @return HttpResponse - */ - public function submitFormReact(array $form): HttpResponse { - return $this->submitForm($form, fn(array $results) => ViewHttpResponse::react('views/DisplayResults.tsx', $results)); - } -} diff --git a/src/Controller/AuthController.php b/src/Controller/Sub/AuthController.php similarity index 97% rename from src/Controller/AuthController.php rename to src/Controller/Sub/AuthController.php index e42c27d..a78b02e 100644 --- a/src/Controller/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -1,8 +1,7 @@ $tactic->getName(), "id" => $tactic->getId()]); } - public function create(): HttpResponse { + public function createNew(): HttpResponse { $tactic = $this->model->makeNewDefault(); return $this->openEditor($tactic); } diff --git a/src/Controller/VisualizerController.php b/src/Controller/Sub/VisualizerController.php similarity index 88% rename from src/Controller/VisualizerController.php rename to src/Controller/Sub/VisualizerController.php index 3c8b55e..e3b5663 100644 --- a/src/Controller/VisualizerController.php +++ b/src/Controller/Sub/VisualizerController.php @@ -1,6 +1,6 @@ tacticModel = $tacticModel; } - public function openVisualizer(int $id): HttpResponse { + public function visualize(int $id): HttpResponse { $tactic = $this->tacticModel->get($id); if ($tactic == null) { @@ -27,6 +27,5 @@ class VisualizerController { } return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]); - } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 930cd67..2a95bcc 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -2,15 +2,47 @@ namespace App\Controller; +use App\Connexion; +use App\Gateway\AuthGateway; +use App\Gateway\TacticInfoGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; +use App\Model\AuthModel; +use App\Model\TacticModel; class UserController { public function home(): HttpResponse { return ViewHttpResponse::twig("home.twig", []); } - public function default(): HttpResponse { - return self::home(); + public function register(): HttpResponse { + $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return (new Sub\AuthController($model))->displayRegister(); + } + return (new Sub\AuthController($model))->confirmRegister($_POST); + } + + public function login(): HttpResponse { + $model = new AuthModel(new AuthGateway(new Connexion(get_database()))); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return (new Sub\AuthController($model))->displayLogin(); + } + return (new Sub\AuthController($model))->confirmLogin($_POST); + } + + public function open(int $id): HttpResponse { + $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + return (new Sub\VisualizerController($model))->visualize($id); + } + + public function edit(int $id): HttpResponse { + $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + return (new Sub\EditorController($model))->edit($id); + } + + public function create(): HttpResponse { + $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + return (new Sub\EditorController($model))->createNew(); } } diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index 45b63e4..312cca8 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -2,7 +2,6 @@ namespace App\Model; -use App\Controller\AuthController; use App\Gateway\AuthGateway; use App\Validation\FieldValidationFail; use App\Validation\ValidationFail; From 5b9096ab35f2cf765700146e7bc34040a27ebfc2 Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Wed, 22 Nov 2023 13:31:25 +0100 Subject: [PATCH 22/38] Correction CI --- src/Controller/UserController.php | 2 -- src/Gateway/TacticInfoGateway.php | 2 +- src/Model/TacticModel.php | 6 ++++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index d93a49b..7fe41ed 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -12,13 +12,11 @@ use App\Model\TacticModel; class UserController { private TacticModel $tacticMdl; - private AuthModel $accountMdl; public function __construct() { $con = new Connexion(get_database()); $this->tacticMdl = new TacticModel(new TacticInfoGateway($con)); - $this->accountMdl = new AuthModel(new AuthGateway($con)); } public function home(): HttpResponse { diff --git a/src/Gateway/TacticInfoGateway.php b/src/Gateway/TacticInfoGateway.php index 4806a04..a9ef5cc 100644 --- a/src/Gateway/TacticInfoGateway.php +++ b/src/Gateway/TacticInfoGateway.php @@ -35,7 +35,7 @@ class TacticInfoGateway { * Return the nb last tactics created * * @param integer $nb - * @return array|null + * @return TacticInfo[] */ public function getLast(int $nb) : ?array { $res = $this->con->fetch( diff --git a/src/Model/TacticModel.php b/src/Model/TacticModel.php index d696b11..aeef9bc 100644 --- a/src/Model/TacticModel.php +++ b/src/Model/TacticModel.php @@ -35,6 +35,12 @@ class TacticModel { return $this->tactics->get($id); } + /** + * Return the nb last tactics created + * + * @param integer $nb + * @return TacticInfo[] + */ public function getLast(int $nb) : ?array { return $this->tactics->getLast($nb); } From 88e547bfcbcbab2cefb05a130882794430fe0cfd Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Wed, 22 Nov 2023 13:32:56 +0100 Subject: [PATCH 23/38] Correction CI --- src/Gateway/TacticInfoGateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gateway/TacticInfoGateway.php b/src/Gateway/TacticInfoGateway.php index a9ef5cc..65c214d 100644 --- a/src/Gateway/TacticInfoGateway.php +++ b/src/Gateway/TacticInfoGateway.php @@ -35,7 +35,7 @@ class TacticInfoGateway { * Return the nb last tactics created * * @param integer $nb - * @return TacticInfo[] + * @return array> */ public function getLast(int $nb) : ?array { $res = $this->con->fetch( From c696eafa38107bb7e593b8f4683d30d6cec8f41e Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Wed, 22 Nov 2023 13:33:56 +0100 Subject: [PATCH 24/38] Correction CI --- src/Model/TacticModel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/TacticModel.php b/src/Model/TacticModel.php index aeef9bc..809c915 100644 --- a/src/Model/TacticModel.php +++ b/src/Model/TacticModel.php @@ -39,7 +39,7 @@ class TacticModel { * Return the nb last tactics created * * @param integer $nb - * @return TacticInfo[] + * @return array> */ public function getLast(int $nb) : ?array { return $this->tactics->getLast($nb); From 851aa72e2c4e6ec8bc42a21cd0c3bb8fac996d64 Mon Sep 17 00:00:00 2001 From: "vivien.dufour" Date: Wed, 22 Nov 2023 08:38:58 +0100 Subject: [PATCH 25/38] fix on routes --- src/Controller/FrontController.php | 49 ++++++++++++--------- src/Controller/Sub/EditorController.php | 4 +- src/Controller/{ => Sub}/TeamController.php | 2 +- src/Controller/UserController.php | 26 +++++++++++ src/Views/display_teams.html.twig | 4 +- src/Views/insert_team.html.twig | 2 +- src/Views/list_team_by_name.html.twig | 2 +- 7 files changed, 61 insertions(+), 28 deletions(-) rename src/Controller/{ => Sub}/TeamController.php (98%) diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 0600a01..7d61b28 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -14,10 +14,12 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; use Twig\Loader\FilesystemLoader; -class FrontController { +class FrontController +{ private AltoRouter $router; - public function __construct(string $basePath) { + public function __construct(string $basePath) + { $this->router = $this->createRouter($basePath); $this->initializeRouterMap(); } @@ -27,7 +29,8 @@ class FrontController { * * @return void */ - public function run(): void { + public function run(): void + { $match = $this->router->match(); if ($match != null) { $this->handleMatch($match); @@ -42,7 +45,8 @@ class FrontController { * @param string $basePath * @return AltoRouter */ - public function createRouter(string $basePath): AltoRouter { + public function createRouter(string $basePath): AltoRouter + { $router = new AltoRouter(); $router->setBasePath($basePath); return $router; @@ -53,17 +57,19 @@ class FrontController { * * @return void */ - private function initializeRouterMap(): void { + private function initializeRouterMap(): void + { $this->router->map("GET", "/", "UserController"); - $this->router->map("GET", "/[a:action]?", "UserController"); - $this->router->map("GET", "/tactic/[a:action]/[i:idTactic]?", "UserController"); + $this->router->map("GET|POST", "/[a:action]?/[i:id]", "UserController"); + $this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); } /** * @param array $match * @return void */ - private function handleMatch(array $match): void { + private function handleMatch(array $match): void + { $tag = $match['target']; $action = $this->getAction($match); @@ -78,15 +84,12 @@ class FrontController { * @param array $params * @return HttpResponse */ - private function tryToCall(string $controller, string $action, array $params): HttpResponse { + private function tryToCall(string $controller, string $action, array $params): HttpResponse + { $controller = $this->getController($controller); - try { - if (is_callable([$controller, $action])) { - return call_user_func_array([$controller, $action], $params); - } else { - return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); - } - } catch (Exception $e) { + if (is_callable([$controller, $action])) { + return call_user_func_array([$controller, $action], $params); + } else { return ViewHttpResponse::twig("error.html.twig", [], HttpCodes::NOT_FOUND); } } @@ -97,7 +100,8 @@ class FrontController { * @param array $match * @return string */ - private function getAction(array $match): string { + private function getAction(array $match): string + { if (isset($match["params"]["action"])) { return $match["params"]["action"]; } @@ -110,7 +114,8 @@ class FrontController { * @param string $controller * @return mixed */ - private function getController(string $controller) { + private function getController(string $controller) + { $namespace = "\\App\\Controller\\"; $controller = $namespace . $controller; return new $controller(); @@ -122,7 +127,8 @@ class FrontController { * @param HttpResponse $response * @return void */ - private function handleResponseByType(HttpResponse $response): void { + private function handleResponseByType(HttpResponse $response): void + { http_response_code($response->getCode()); if ($response instanceof ViewHttpResponse) { $this->displayViewByKind($response); @@ -138,7 +144,8 @@ class FrontController { * @param ViewHttpResponse $response * @return void */ - private function displayViewByKind(ViewHttpResponse $response): void { + private function displayViewByKind(ViewHttpResponse $response): void + { $file = $response->getFile(); $args = $response->getArguments(); @@ -151,7 +158,7 @@ class FrontController { $loader = new FilesystemLoader('../src/Views/'); $twig = new Environment($loader); $twig->display($file, $args); - } catch (RuntimeError|SyntaxError|LoaderError $e) { + } catch (RuntimeError | SyntaxError | LoaderError $e) { http_response_code(500); echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); throw $e; diff --git a/src/Controller/Sub/EditorController.php b/src/Controller/Sub/EditorController.php index def6ac9..ce7a0a3 100644 --- a/src/Controller/Sub/EditorController.php +++ b/src/Controller/Sub/EditorController.php @@ -14,8 +14,8 @@ use App\Model\TacticModel; class EditorController { private TacticModel $model; - public function __construct() { - $this->model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + public function __construct(TacticModel $model) { + $this->model = $model; } private function openEditor(TacticInfo $tactic): HttpResponse { diff --git a/src/Controller/TeamController.php b/src/Controller/Sub/TeamController.php similarity index 98% rename from src/Controller/TeamController.php rename to src/Controller/Sub/TeamController.php index 08e22cc..c3991a3 100644 --- a/src/Controller/TeamController.php +++ b/src/Controller/Sub/TeamController.php @@ -1,6 +1,6 @@ createNew(); } + + public function createTeam(): HttpResponse { + $model = new TeamModel(new TeamGateway(new Connexion(get_database()))); + $ctrl = new Sub\TeamController($model); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return $ctrl->displaySubmitTeam(); + } + return $ctrl->submitTeam($_POST); + } + + public function listTeams(): HttpResponse { + $model = new TeamModel(new TeamGateway(new Connexion(get_database()))); + $ctrl = new Sub\TeamController($model); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return $ctrl->displayListTeamByName(); + } + return $ctrl->listTeamByName($_POST); + } + + public function getTeam(int $id): HttpResponse { + $model = new TeamModel(new TeamGateway(new Connexion(get_database()))); + $ctrl = new Sub\TeamController($model); + return $ctrl->getTeam($id); + } } diff --git a/src/Views/display_teams.html.twig b/src/Views/display_teams.html.twig index c0ac185..47b3cbc 100644 --- a/src/Views/display_teams.html.twig +++ b/src/Views/display_teams.html.twig @@ -10,7 +10,7 @@

Aucune équipe n'a été trouvée

Chercher une équipe

-
+
@@ -22,7 +22,7 @@
{% else %} {% for t in teams %} -
+

Nom de l'équipe : {{ t.name }}

logo de l'équipe
diff --git a/src/Views/insert_team.html.twig b/src/Views/insert_team.html.twig index 0cc85dd..fbe6917 100644 --- a/src/Views/insert_team.html.twig +++ b/src/Views/insert_team.html.twig @@ -64,7 +64,7 @@

Créer une équipe

- +
diff --git a/src/Views/list_team_by_name.html.twig b/src/Views/list_team_by_name.html.twig index 1d9ddf3..1165da3 100644 --- a/src/Views/list_team_by_name.html.twig +++ b/src/Views/list_team_by_name.html.twig @@ -62,7 +62,7 @@

Chercher une équipe

- +
From ad4208e6dafca2573be166bcdcf852883481e98e Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 22 Nov 2023 13:46:08 +0100 Subject: [PATCH 26/38] fix format --- src/Controller/FrontController.php | 33 ++++++++++-------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/src/Controller/FrontController.php b/src/Controller/FrontController.php index 7d61b28..eb796f7 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/FrontController.php @@ -14,12 +14,10 @@ use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; use Twig\Loader\FilesystemLoader; -class FrontController -{ +class FrontController { private AltoRouter $router; - public function __construct(string $basePath) - { + public function __construct(string $basePath) { $this->router = $this->createRouter($basePath); $this->initializeRouterMap(); } @@ -29,8 +27,7 @@ class FrontController * * @return void */ - public function run(): void - { + public function run(): void { $match = $this->router->match(); if ($match != null) { $this->handleMatch($match); @@ -45,8 +42,7 @@ class FrontController * @param string $basePath * @return AltoRouter */ - public function createRouter(string $basePath): AltoRouter - { + public function createRouter(string $basePath): AltoRouter { $router = new AltoRouter(); $router->setBasePath($basePath); return $router; @@ -57,8 +53,7 @@ class FrontController * * @return void */ - private function initializeRouterMap(): void - { + private function initializeRouterMap(): void { $this->router->map("GET", "/", "UserController"); $this->router->map("GET|POST", "/[a:action]?/[i:id]", "UserController"); $this->router->map("GET|POST", "/tactic/[a:action]/[i:idTactic]?", "UserController"); @@ -68,8 +63,7 @@ class FrontController * @param array $match * @return void */ - private function handleMatch(array $match): void - { + private function handleMatch(array $match): void { $tag = $match['target']; $action = $this->getAction($match); @@ -84,8 +78,7 @@ class FrontController * @param array $params * @return HttpResponse */ - private function tryToCall(string $controller, string $action, array $params): HttpResponse - { + private function tryToCall(string $controller, string $action, array $params): HttpResponse { $controller = $this->getController($controller); if (is_callable([$controller, $action])) { return call_user_func_array([$controller, $action], $params); @@ -100,8 +93,7 @@ class FrontController * @param array $match * @return string */ - private function getAction(array $match): string - { + private function getAction(array $match): string { if (isset($match["params"]["action"])) { return $match["params"]["action"]; } @@ -114,8 +106,7 @@ class FrontController * @param string $controller * @return mixed */ - private function getController(string $controller) - { + private function getController(string $controller) { $namespace = "\\App\\Controller\\"; $controller = $namespace . $controller; return new $controller(); @@ -127,8 +118,7 @@ class FrontController * @param HttpResponse $response * @return void */ - private function handleResponseByType(HttpResponse $response): void - { + private function handleResponseByType(HttpResponse $response): void { http_response_code($response->getCode()); if ($response instanceof ViewHttpResponse) { $this->displayViewByKind($response); @@ -144,8 +134,7 @@ class FrontController * @param ViewHttpResponse $response * @return void */ - private function displayViewByKind(ViewHttpResponse $response): void - { + private function displayViewByKind(ViewHttpResponse $response): void { $file = $response->getFile(); $args = $response->getArguments(); From d54559e01d3d734d87c95607d2dfbc5a2a616695 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 22 Nov 2023 14:43:03 +0100 Subject: [PATCH 27/38] fix --- src/Views/insert_team.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/insert_team.html.twig b/src/Views/insert_team.html.twig index fbe6917..0fc59ca 100644 --- a/src/Views/insert_team.html.twig +++ b/src/Views/insert_team.html.twig @@ -64,7 +64,7 @@

Créer une équipe

- +
From 28171b35e0e9bdb548b031040054b51f5699083c Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Wed, 22 Nov 2023 14:43:29 +0100 Subject: [PATCH 28/38] Adaptation welcome page --- src/Views/home.twig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Views/home.twig b/src/Views/home.twig index 1c14e02..1429363 100644 --- a/src/Views/home.twig +++ b/src/Views/home.twig @@ -73,13 +73,13 @@

Mes strategies

- + {% if recentTactic != null %} {% for tactic in recentTactic %} -
+

{{tactic.id}} - {{tactic.name}} - {{tactic.creation_date}}

- +
{% endfor %} {% else %} From 19d2ed01993723749e2a13f27710a4ee816e378e Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Wed, 22 Nov 2023 14:52:33 +0100 Subject: [PATCH 29/38] Merging in progress --- src/Controller/UserController.php | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index c5f9ef2..3bc3d71 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -3,15 +3,18 @@ namespace App\Controller; use App\Connexion; -<<<<<<< HEAD use App\Gateway\AuthGateway; use App\Gateway\TacticInfoGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; use App\Model\AuthModel; use App\Model\TacticModel; +use App\Gateway\TeamGateway; +use App\Model\TeamModel; +use App\Session\SessionHandle; +use App\Controller\Sub\EditorController; -class UserController { +class UserController extends VisitorController { private TacticModel $tacticMdl; public function __construct() @@ -20,18 +23,6 @@ class UserController { $this->tacticMdl = new TacticModel(new TacticInfoGateway($con)); } -======= -use App\Gateway\TacticInfoGateway; -use App\Gateway\TeamGateway; -use App\Http\HttpResponse; -use App\Http\ViewHttpResponse; -use App\Model\TacticModel; -use App\Model\TeamModel; -use App\Session\SessionHandle; - -class UserController extends VisitorController { - ->>>>>>> d54559e01d3d734d87c95607d2dfbc5a2a616695 public function home(): HttpResponse { $listTactic = $this->tacticMdl->getLast(5); return ViewHttpResponse::twig("home.twig", ["recentTactic" => $listTactic]); @@ -44,12 +35,12 @@ class UserController extends VisitorController { public function edit(int $id, SessionHandle $session): HttpResponse { $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new Sub\EditorController($model))->edit($id, $session); + return (new EditorController($model))->edit($id, $session); } public function create(SessionHandle $session): HttpResponse { $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new Sub\EditorController($model))->createNew($session); + return (new EditorController($model))->createNew($session); } public function open(int $id, SessionHandle $session): HttpResponse { From 83a19fd93d0c5110e9bf2c43eb1c3028e7126243 Mon Sep 17 00:00:00 2001 From: samuel Date: Wed, 22 Nov 2023 12:58:03 +0100 Subject: [PATCH 30/38] add error messages --- sql/setup-tables.sql | 7 ------- src/Controller/Sub/AuthController.php | 14 ++++---------- src/Controller/UserController.php | 21 ++------------------- src/Model/AuthModel.php | 8 ++++---- src/Validation/Validators.php | 4 ++-- src/Views/display_login.html.twig | 18 ++++++++++++------ src/Views/display_register.html.twig | 14 ++++++++++++-- 7 files changed, 36 insertions(+), 50 deletions(-) diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index e105a1e..abf5049 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -44,10 +44,3 @@ CREATE TABLE Member FOREIGN KEY (idTeam) REFERENCES Team (id), FOREIGN KEY (idMember) REFERENCES User (id) ); - -CREATE TABLE TacticInfo -( - id integer PRIMARY KEY AUTOINCREMENT, - name varchar, - creation_date timestamp DEFAULT CURRENT_TIMESTAMP -); diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/Sub/AuthController.php index d94da43..709be3e 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/Sub/AuthController.php @@ -30,14 +30,8 @@ class AuthController { * @param ValidationFail[] $fails * @return HttpResponse */ - private function displayBadFields(string $viewName, array $fails): HttpResponse { - $bad_fields = []; - foreach ($fails as $err) { - if ($err instanceof FieldValidationFail) { - $bad_fields[] = $err->getFieldName(); - } - } - return ViewHttpResponse::twig($viewName, ['bad_fields' => $bad_fields]); + private function displayBadFields(string $viewName, array $fails): HttpResponse{ + return ViewHttpResponse::twig($viewName, ['fails' => $fails]); } /** @@ -51,7 +45,7 @@ class AuthController { "username" => [Validators::name(), Validators::lenBetween(2, 32)], "password" => [Validators::lenBetween(6, 256)], "confirmpassword" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/","invalide"),Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_register.html.twig", $fails); @@ -80,7 +74,7 @@ class AuthController { $fails = []; $request = HttpRequest::from($request, $fails, [ "password" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/","invalide"),Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_login.html.twig", $fails); diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index c5f9ef2..58cc2a8 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -3,24 +3,7 @@ namespace App\Controller; use App\Connexion; -<<<<<<< HEAD -use App\Gateway\AuthGateway; -use App\Gateway\TacticInfoGateway; -use App\Http\HttpResponse; -use App\Http\ViewHttpResponse; -use App\Model\AuthModel; -use App\Model\TacticModel; -class UserController { - private TacticModel $tacticMdl; - - public function __construct() - { - $con = new Connexion(get_database()); - $this->tacticMdl = new TacticModel(new TacticInfoGateway($con)); - } - -======= use App\Gateway\TacticInfoGateway; use App\Gateway\TeamGateway; use App\Http\HttpResponse; @@ -31,9 +14,9 @@ use App\Session\SessionHandle; class UserController extends VisitorController { ->>>>>>> d54559e01d3d734d87c95607d2dfbc5a2a616695 public function home(): HttpResponse { - $listTactic = $this->tacticMdl->getLast(5); + $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); + $listTactic = $model->getLast(5); return ViewHttpResponse::twig("home.twig", ["recentTactic" => $listTactic]); } diff --git a/src/Model/AuthModel.php b/src/Model/AuthModel.php index 1739ab5..bd359ba 100644 --- a/src/Model/AuthModel.php +++ b/src/Model/AuthModel.php @@ -29,11 +29,11 @@ class AuthModel { public function register(string $username, string $password, string $confirmPassword, string $email, array &$failures): ?Account { if ($password != $confirmPassword) { - $failures[] = new FieldValidationFail("confirmpassword", "password and password confirmation are not equals"); + $failures[] = new FieldValidationFail("confirmpassword", "Le mot de passe et la confirmation ne sont pas les mêmes."); } if ($this->gateway->exists($email)) { - $failures[] = new FieldValidationFail("email", "email already exist"); + $failures[] = new FieldValidationFail("email", "L'email existe déjà"); } if (!empty($failures)) { @@ -59,13 +59,13 @@ class AuthModel { */ public function login(string $email, string $password, array &$failures): ?Account { if (!$this->gateway->exists($email)) { - $failures[] = new FieldValidationFail("email", "email doesnt exists"); + $failures[] = new FieldValidationFail("email", "Vous n'êtes pas enregistré."); return null; } $hash = $this->gateway->getHash($email); if (!password_verify($password, $hash)) { - $failures[] = new FieldValidationFail("password", "invalid password"); + $failures[] = new FieldValidationFail("password", "Mot de passe invalide."); return null; } diff --git a/src/Validation/Validators.php b/src/Validation/Validators.php index cb28007..6b72ff0 100644 --- a/src/Validation/Validators.php +++ b/src/Validation/Validators.php @@ -41,10 +41,10 @@ class Validators { function (string $fieldName, string $str) use ($min, $max) { $len = strlen($str); if ($len >= $max) { - return [new FieldValidationFail($fieldName, "field is longer than $max chars.")]; + return [new FieldValidationFail($fieldName, "trop long, maximum $max caractères.")]; } if ($len < $min) { - return [new FieldValidationFail($fieldName, "field is shorted than $min chars.")]; + return [new FieldValidationFail($fieldName, "trop court, minimum $min caractères.")]; } return []; } diff --git a/src/Views/display_login.html.twig b/src/Views/display_login.html.twig index 33b2385..ca6890d 100644 --- a/src/Views/display_login.html.twig +++ b/src/Views/display_login.html.twig @@ -53,26 +53,32 @@ background-color: #0056b3; } - {% for err in bad_fields %} - .form-group #{{ err }} { + .error-messages{ + color : #ff331a; + font-style: italic; + } + + {% for err in fails %} + .form-group #{{ err.getFieldName() }} { border-color: red; } {% endfor %} - - -

Se connecter

+ + {% for name in fails %} + + {% endfor %} + -
diff --git a/src/Views/display_register.html.twig b/src/Views/display_register.html.twig index 40199a0..2b24e23 100644 --- a/src/Views/display_register.html.twig +++ b/src/Views/display_register.html.twig @@ -49,12 +49,17 @@ cursor: pointer; } + .error-messages{ + color : #ff331a; + font-style: italic; + } + input[type="submit"]:hover { background-color: #0056b3; } - {% for err in bad_fields %} - .form-group #{{ err }} { + {% for err in fails %} + .form-group #{{ err.getFieldName() }} { border-color: red; } {% endfor %} @@ -67,6 +72,11 @@

S'enregistrer

+ + {% for name in fails %} + + {% endfor %} + From a0208115183bbda3152408a2fa75cfc33e239902 Mon Sep 17 00:00:00 2001 From: Vivien DUFOUR Date: Wed, 22 Nov 2023 15:19:16 +0100 Subject: [PATCH 31/38] team/addMembers (#21) Co-authored-by: vivien.dufour Reviewed-on: https://codefirst.iut.uca.fr/git/IQBall/Application-Web/pulls/21 --- public/index.php | 1 + sql/setup-tables.sql | 12 ++- src/Controller/Sub/TeamController.php | 36 ++++++++- src/Controller/UserController.php | 19 +++++ src/Data/Account.php | 2 - src/Data/Member.php | 18 ++++- src/Data/Team.php | 4 + src/Gateway/AuthGateway.php | 47 ++++++++++++ src/Gateway/TeamGateway.php | 42 +++++++++-- src/Model/TeamModel.php | 23 ++++-- src/Views/add_member.html.twig | 103 ++++++++++++++++++++++++++ src/Views/delete_member.html.twig | 73 ++++++++++++++++++ src/Views/display_team.html.twig | 11 ++- src/Views/display_teams.html.twig | 4 +- src/Views/list_team_by_name.html.twig | 2 +- 15 files changed, 365 insertions(+), 32 deletions(-) create mode 100644 src/Gateway/AuthGateway.php create mode 100644 src/Views/add_member.html.twig create mode 100644 src/Views/delete_member.html.twig diff --git a/public/index.php b/public/index.php index cd4868d..d8dd098 100644 --- a/public/index.php +++ b/public/index.php @@ -11,6 +11,7 @@ use App\Controller\FrontController; use App\Session\PhpSessionHandle; $basePath = get_public_path(); + $frontController = new FrontController($basePath); $frontController->run(PhpSessionHandle::init()); diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index abf5049..b13712f 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -22,6 +22,9 @@ CREATE TABLE Tactic FOREIGN KEY (owner) REFERENCES Account ); +CREATE TABLE FormEntries(name varchar, description varchar); + + CREATE TABLE Team ( id integer PRIMARY KEY AUTOINCREMENT, @@ -31,14 +34,9 @@ CREATE TABLE Team secondColor varchar ); -CREATE TABLE User -( - id integer PRIMARY KEY AUTOINCREMENT -); -CREATE TABLE Member -( - idTeam integer, +CREATE TABLE Member( + idTeam integer, idMember integer, role char(1) CHECK (role IN ('C', 'P')), FOREIGN KEY (idTeam) REFERENCES Team (id), diff --git a/src/Controller/Sub/TeamController.php b/src/Controller/Sub/TeamController.php index c3991a3..adefa41 100644 --- a/src/Controller/Sub/TeamController.php +++ b/src/Controller/Sub/TeamController.php @@ -23,11 +23,17 @@ class TeamController { return ViewHttpResponse::twig("insert_team.html.twig", []); } - /** - * @param array $request - * @return HttpResponse - */ + + public function displayAddMember() : HttpResponse { + return ViewHttpResponse::twig("add_member.html.twig", []); + } + + public function displayDeleteMember() : HttpResponse { + return ViewHttpResponse::twig("delete_member.html.twig", []); + } + public function submitTeam(array $request): HttpResponse { + $errors = []; $request = HttpRequest::from($request, $errors, [ @@ -80,4 +86,26 @@ class TeamController { $result = $this->model->displayTeam($id); return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]); } + + public function addMember(array $request) : HttpResponse { + $errors = []; + + $request = HttpRequest::from($request, $errors, [ + "team" => [Validators::isInteger()], + "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"),Validators::lenBetween(5, 256)] + ]); + + return $this->getTeam($this->model->addMember($request['mail'], intval($request['team']), $request['role'])); + } + + public function deleteMember(array $request) : HttpResponse { + $errors = []; + + $request = HttpRequest::from($request, $errors, [ + "team" => [Validators::isInteger()], + "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"),Validators::lenBetween(5, 256)] + ]); + + return $this->getTeam($this->model->deleteMember($request['mail'], intval($request['team']))); + } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 674f81e..46f4992 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -62,4 +62,23 @@ class UserController extends VisitorController { $ctrl = new Sub\TeamController($model); return $ctrl->getTeam($id); } + + public function addMember(): HttpResponse { + $model = new TeamModel(new TeamGateway(new Connexion(get_database()))); + $ctrl = new Sub\TeamController($model); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return $ctrl->displayAddMember($_POST); + } + return $ctrl->addMember($_POST); + } + + public function deleteMember(): HttpResponse { + $model = new TeamModel(new TeamGateway(new Connexion(get_database()))); + $ctrl = new Sub\TeamController($model); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + return $ctrl->displayDeleteMember($_POST); + } + return $ctrl->deleteMember($_POST); + } + } diff --git a/src/Data/Account.php b/src/Data/Account.php index 0ed4339..6a61563 100755 --- a/src/Data/Account.php +++ b/src/Data/Account.php @@ -2,8 +2,6 @@ namespace App\Data; -use http\Exception\InvalidArgumentException; - /** * Base class of a user account. * Contains the private information that we don't want diff --git a/src/Data/Member.php b/src/Data/Member.php index b415f65..3049fde 100755 --- a/src/Data/Member.php +++ b/src/Data/Member.php @@ -7,10 +7,15 @@ namespace App\Data; */ class Member { /** - * @var int The member's user id + * @var AccountUser The member's user account */ private int $userId; + /** + * @var int The member's team id + */ + private int $teamId; + /** * @var MemberRole the member's role */ @@ -20,8 +25,9 @@ class Member { * @param int $userId * @param MemberRole $role */ - public function __construct(int $userId, MemberRole $role) { + public function __construct(int $userId, int $teamId, MemberRole $role) { $this->userId = $userId; + $this->teamId = $teamId; $this->role = $role; } @@ -39,4 +45,12 @@ class Member { public function getRole(): MemberRole { return $this->role; } + + /** + * @return int + */ + public function getTeamId(): int + { + return $this->teamId; + } } diff --git a/src/Data/Team.php b/src/Data/Team.php index e50ad40..a359722 100755 --- a/src/Data/Team.php +++ b/src/Data/Team.php @@ -72,4 +72,8 @@ class Team { return $this->members; } + public function addMember(Member $m) { + $this->members[] = $m; + } + } diff --git a/src/Gateway/AuthGateway.php b/src/Gateway/AuthGateway.php new file mode 100644 index 0000000..3370d26 --- /dev/null +++ b/src/Gateway/AuthGateway.php @@ -0,0 +1,47 @@ +con = $con; + } + + + public function mailExist(string $email): bool { + return $this->getUserFields($email) != null; + } + + + public function insertAccount(string $username, string $hash, string $email): void { + $this->con->exec("INSERT INTO AccountUser(username, hash, email) VALUES (:username,:hash,:email)", [':username' => [$username, PDO::PARAM_STR],':hash' => [$hash, PDO::PARAM_STR],':email' => [$email, PDO::PARAM_STR]]); + } + + public function getUserHash(string $email): string { + $results = $this->con->fetch("SELECT hash FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); + return $results[0]['hash']; + } + + + /** + * @param string $email + * @return array|null + */ + public function getUserFields(string $email): ?array { + $results = $this->con->fetch("SELECT username,email FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); + $firstRow = $results[0] ?? null; + return $firstRow; + } + + + + +} diff --git a/src/Gateway/TeamGateway.php b/src/Gateway/TeamGateway.php index c3d22dc..c98d803 100644 --- a/src/Gateway/TeamGateway.php +++ b/src/Gateway/TeamGateway.php @@ -24,10 +24,19 @@ class TeamGateway { ); } - /** - * @param string $name - * @return array[] - */ + + public function insertMember(int $idTeam, int $idMember, string $role) { + $this->con->exec( + "INSERT INTO Member(idTeam, idMember, role) VALUES (:idTeam , :idMember, :role)", + [ + ":idTeam" => [$idTeam, PDO::PARAM_INT], + ":idMember" => [$idMember, PDO::PARAM_INT], + ":role" => [$role, PDO::PARAM_STR] + ] + ); + } + + public function listByName(string $name): array { return $this->con->fetch( "SELECT id,name,picture,mainColor,secondColor FROM Team WHERE name LIKE '%' || :name || '%'", @@ -69,11 +78,30 @@ class TeamGateway { */ public function getMembersById(int $id): array { return $this->con->fetch( - "SELECT m.role,u.id FROM User u,Team t,Member m WHERE t.id = :id AND m.idTeam = t.id AND m.idMember = u.id", - [ - ":id" => [$id, PDO::PARAM_INT], + "SELECT a.id,m.role,a.email,a.username FROM Account a,Team t,Member m WHERE t.id = :id AND m.idTeam = t.id AND m.idMember = a.id", + [ + ":id" => [$id, PDO::PARAM_INT] ] ); } + public function getMemberIdByMail($mail) : array { + return $this->con->fetch( + "SELECT id FROM Account WHERE email = :mail", + [ + ":mail" => [$mail, PDO::PARAM_STR] + ] + ); + } + + public function deleteMember(int $idTeam, int $idMember) { + $this->con->exec( + "DELETE FROM Member WHERE idTeam = :idTeam AND idMember = :idMember", + [ + ":idTeam" => [$idTeam, PDO::PARAM_INT], + ":idMember" => [$idMember, PDO::PARAM_INT], + ] + ); + } + } diff --git a/src/Model/TeamModel.php b/src/Model/TeamModel.php index 65f22a7..c936d30 100644 --- a/src/Model/TeamModel.php +++ b/src/Model/TeamModel.php @@ -24,10 +24,14 @@ class TeamModel { return intval($result[0]['id']); } - /** - * @param string $name - * @return Team[] - */ + + public function addMember(string $mail, int $teamId, string $role) : int { + $result = $this->gateway->getMemberIdByMail($mail)[0]; + $memberId = intval($result['id']); + $this->gateway->insertMember($teamId, $memberId, $role); + return $teamId; + } + public function listByName(string $name): array { $teams = []; $results = $this->gateway->listByName($name); @@ -42,13 +46,22 @@ class TeamModel { $result = $this->gateway->getTeamById($id)[0]; $resultMembers = $this->gateway->getMembersById($id); foreach ($resultMembers as $row) { + var_dump($row['role']); if ($row['role'] == 'C') { $role = MemberRole::coach(); } else { $role = MemberRole::player(); } - $members[] = new Member($row['id'], $role); + $members[] = new Member($row['id'], $id, $role); } return new Team(intval($result['id']), $result['name'], $result['picture'], Color::from($result['mainColor']), Color::from($result['secondColor']), $members); } + + public function deleteMember(string $mail, int $teamId) : int { + $result = $this->gateway->getMemberIdByMail($mail)[0]; + $memberId = intval($result['id']); + $this->gateway->deleteMember($teamId, $memberId); + return $teamId; + } + } diff --git a/src/Views/add_member.html.twig b/src/Views/add_member.html.twig new file mode 100644 index 0000000..ef74da2 --- /dev/null +++ b/src/Views/add_member.html.twig @@ -0,0 +1,103 @@ + + + + + Ajouter un membre + + + + +
+

Ajouter un membre à votre équipe

+ +
+ + + + + +
+ Rôle du membre dans l'équipe : +
+ + +
+
+ + +
+
+ +
+
+ +
+ +
+ + + \ No newline at end of file diff --git a/src/Views/delete_member.html.twig b/src/Views/delete_member.html.twig new file mode 100644 index 0000000..a478284 --- /dev/null +++ b/src/Views/delete_member.html.twig @@ -0,0 +1,73 @@ + + + + + Ajouter un membre + + + + +
+

Supprimez un membre de votre équipe

+
+
+ + + + +
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/Views/display_team.html.twig b/src/Views/display_team.html.twig index ada8566..474397e 100644 --- a/src/Views/display_team.html.twig +++ b/src/Views/display_team.html.twig @@ -54,6 +54,7 @@ height: 80px; width: 80px; } + @@ -72,8 +73,14 @@

Couleur principale :

Couleur secondaire :

- {% for m in team.members %} -

m.id

+ + {% for m in team.listMembers() %} +

{{ m.getUserId() }}

+ {% if m.getRole().isCoach() %} +

: Coach

+ {% else %} +

: Joueur

+ {% endif %} {% endfor %}
diff --git a/src/Views/display_teams.html.twig b/src/Views/display_teams.html.twig index 47b3cbc..bf89909 100644 --- a/src/Views/display_teams.html.twig +++ b/src/Views/display_teams.html.twig @@ -10,7 +10,7 @@

Aucune équipe n'a été trouvée

Chercher une équipe

-
+
@@ -22,7 +22,7 @@
{% else %} {% for t in teams %} -
+

Nom de l'équipe : {{ t.name }}

logo de l'équipe
diff --git a/src/Views/list_team_by_name.html.twig b/src/Views/list_team_by_name.html.twig index 1165da3..0ea1905 100644 --- a/src/Views/list_team_by_name.html.twig +++ b/src/Views/list_team_by_name.html.twig @@ -62,7 +62,7 @@

Chercher une équipe

- +
From 34777e70df3d900675f8b90f1b0f2751240cdc5e Mon Sep 17 00:00:00 2001 From: DahmaneYanis Date: Wed, 22 Nov 2023 15:27:46 +0100 Subject: [PATCH 32/38] Add settings --- src/Controller/UserController.php | 4 ++++ src/Views/account_settings.twig | 24 ++++++++++++++++++++++++ src/Views/home.twig | 12 +++++++----- 3 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 src/Views/account_settings.twig diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 674f81e..428e20f 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -19,6 +19,10 @@ class UserController extends VisitorController { return ViewHttpResponse::twig("home.twig", ["recentTactic" => $listTactic]); } + public function settings(): HttpResponse { + return ViewHttpResponse::twig("account_settings.twig", []); + } + public function view(int $id, SessionHandle $session): HttpResponse { $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); return (new Sub\VisualizerController($model))->visualize($id, $session); diff --git a/src/Views/account_settings.twig b/src/Views/account_settings.twig new file mode 100644 index 0000000..3d8ead3 --- /dev/null +++ b/src/Views/account_settings.twig @@ -0,0 +1,24 @@ + + + + + + + Paramètres + + + + + + + + +

Paramètres

+ + \ No newline at end of file diff --git a/src/Views/home.twig b/src/Views/home.twig index 1429363..6912c89 100644 --- a/src/Views/home.twig +++ b/src/Views/home.twig @@ -6,6 +6,7 @@ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> Page d'accueil + +

IQ Ball

-
+
Account logo Date: Wed, 22 Nov 2023 15:35:23 +0100 Subject: [PATCH 33/38] Add button --- src/Views/home.twig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Views/home.twig b/src/Views/home.twig index 6912c89..35379ce 100644 --- a/src/Views/home.twig +++ b/src/Views/home.twig @@ -63,6 +63,8 @@

Mes équipes

+ + {% if recentTeam != null %} {% for team in recentTeam %}
From 36051ebd830d81c0a67c0d9f05f5ed67b468aa43 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Fri, 24 Nov 2023 01:59:08 +0100 Subject: [PATCH 34/38] good routes, good controllers --- .../assets/icon}/account.png | Bin .../assets/icon}/account.svg | 0 public/api/index.php | 31 +++--- public/index.php | 87 ++++++++++++++++- src/Controller/{Sub => }/AuthController.php | 3 +- src/Controller/{Sub => }/EditorController.php | 7 +- src/Controller/Route/Action.php | 71 ++++++++++++++ .../{ => Route}/FrontController.php | 80 +++------------- src/Controller/{Sub => }/TeamController.php | 31 +++--- src/Controller/UserController.php | 90 ++++-------------- src/Controller/VisitorController.php | 28 ------ .../{Sub => }/VisualizerController.php | 5 +- src/Data/Color.php | 3 - src/Views/add_member.html.twig | 2 +- src/Views/delete_member.html.twig | 2 +- src/Views/display_teams.html.twig | 4 +- src/Views/home.twig | 12 +-- src/Views/insert_team.html.twig | 2 +- src/Views/list_team_by_name.html.twig | 2 +- 19 files changed, 238 insertions(+), 222 deletions(-) rename {public/img/welcomePage => front/assets/icon}/account.png (100%) rename {public/img/welcomePage => front/assets/icon}/account.svg (100%) rename src/Controller/{Sub => }/AuthController.php (97%) rename src/Controller/{Sub => }/EditorController.php (87%) create mode 100644 src/Controller/Route/Action.php rename src/Controller/{ => Route}/FrontController.php (61%) rename src/Controller/{Sub => }/TeamController.php (68%) delete mode 100644 src/Controller/VisitorController.php rename src/Controller/{Sub => }/VisualizerController.php (90%) diff --git a/public/img/welcomePage/account.png b/front/assets/icon/account.png similarity index 100% rename from public/img/welcomePage/account.png rename to front/assets/icon/account.png diff --git a/public/img/welcomePage/account.svg b/front/assets/icon/account.svg similarity index 100% rename from public/img/welcomePage/account.svg rename to front/assets/icon/account.svg diff --git a/public/api/index.php b/public/api/index.php index c5f8f2e..cb278df 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -30,7 +30,8 @@ function getAuthController(): APIAuthController { /** * A Front controller action */ -class Action { +//TODO workaround for generic Action +class ApiAction { /** * @var callable(mixed[]): HttpResponse $action action to call */ @@ -69,18 +70,18 @@ class Action { /** * @param callable(mixed[]): HttpResponse $action - * @return Action an action that does not require to have an authorization. + * @return ApiAction an action that does not require to have an authorization. */ - public static function noAuth(callable $action): Action { - return new Action($action, false); + public static function noAuth(callable $action): ApiAction { + return new ApiAction($action, false); } /** * @param callable(mixed[]): HttpResponse $action - * @return Action an action that does require to have an authorization. + * @return ApiAction an action that does require to have an authorization. */ - public static function auth(callable $action): Action { - return new Action($action, true); + public static function auth(callable $action): ApiAction { + return new ApiAction($action, true); } } @@ -95,23 +96,23 @@ function handleMatch(array $match): HttpResponse { } $action = $match['target']; - if (!$action instanceof Action) { + if (!$action instanceof ApiAction) { throw new Exception("routed action is not an Action object."); } $auth = null; if ($action->isAuthRequired()) { - $auth = tryGetAuthAccount(); + $auth = tryGetAuthorization(); if ($auth == null) { - return new JsonHttpResponse([ValidationFail::unauthorized("Missing or invalid 'Authorization' header")]); + return new JsonHttpResponse([ValidationFail::unauthorized("Missing or invalid 'Authorization' header.")]); } } return $action->run($match['params'], $auth); } -function tryGetAuthAccount(): ?Account { +function tryGetAuthorization(): ?Account { $headers = getallheaders(); // If no authorization header is set, try fallback to php session. @@ -128,10 +129,10 @@ function tryGetAuthAccount(): ?Account { $router = new AltoRouter(); $router->setBasePath(get_public_path() . "/api"); -$router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); -$router->map("GET", "/tactic/[i:id]", Action::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc))); -$router->map("POST", "/tactic/new", Action::auth(fn(Account $acc) => getTacticController()->newTactic($acc))); -$router->map("POST", "/auth", Action::noAuth(fn() => getAuthController()->authorize())); +$router->map("POST", "/tactic/[i:id]/edit/name", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); +$router->map("GET", "/tactic/[i:id]", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc))); +$router->map("POST", "/tactic/new", ApiAction::auth(fn(Account $acc) => getTacticController()->newTactic($acc))); +$router->map("POST", "/auth", ApiAction::noAuth(fn() => getAuthController()->authorize())); $match = $router->match(); diff --git a/public/index.php b/public/index.php index d8dd098..8d1f148 100644 --- a/public/index.php +++ b/public/index.php @@ -7,12 +7,93 @@ require "../sql/database.php"; require "utils.php"; require "../src/react-display.php"; -use App\Controller\FrontController; +use App\Controller\AuthController; +use App\Controller\EditorController; +use App\Controller\Route\Action; +use App\Controller\Route\FrontController; +use App\Controller\TeamController; +use App\Controller\UserController; +use App\Controller\VisualizerController; +use App\Gateway\AccountGateway; +use App\Gateway\TacticInfoGateway; +use App\Gateway\TeamGateway; +use App\Model\AuthModel; +use App\Model\TacticModel; +use App\Model\TeamModel; +use App\Session\MutableSessionHandle; use App\Session\PhpSessionHandle; +use App\Connexion; +use App\Session\SessionHandle; +$connexion = new Connexion(get_database()); + +function getUserController(): UserController { + global $connexion; + return new UserController(new TacticModel(new TacticInfoGateway($connexion))); +} + +function getVisualizerController(): VisualizerController { + global $connexion; + return new VisualizerController(new TacticModel(new TacticInfoGateway($connexion))); +} + +function getEditorController(): EditorController { + global $connexion; + return new EditorController(new TacticModel(new TacticInfoGateway($connexion))); +} + +function getTeamController(): TeamController { + global $connexion; + return new TeamController(new TeamModel(new TeamGateway($connexion))); +} + +function getAuthController(): AuthController { + global $connexion; + return new AuthController(new AuthModel(new AccountGateway($connexion))); +} + +function initFrontController(FrontController $fc) { + //authentication + $fc->addRoute("GET", "/login", Action::noAuth(fn() => getAuthController()->displayLogin())); + $fc->addRoute("GET", "/register", Action::noAuth(fn() => getAuthController()->displayRegister())); + $fc->addRoute("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s))); + $fc->addRoute("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s))); + + //user-related + $fc->addRoute("GET", "/home", Action::auth(fn(SessionHandle $s) => getUserController()->home($s))); + $fc->addRoute("GET", "/settings", Action::auth(fn(SessionHandle $s) => getUserController()->settings($s))); + + //tactic-related + $fc->addRoute("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s))); + $fc->addRoute("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s))); + $fc->addRoute("GET", "/tactic/new", Action::auth(fn(SessionHandle $s) => getEditorController()->createNew($s))); + + //team-related + $fc->addRoute("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); + $fc->addRoute("POST", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->submitTeam($_POST, $s))); + $fc->addRoute("GET", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->displayListTeamByName($s))); + $fc->addRoute("POST", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->listTeamByName($_POST, $s))); + $fc->addRoute("GET", "/team/[i:id]", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->displayTeam($id, $s))); + $fc->addRoute("GET", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->displayAddMember($s))); + $fc->addRoute("POST", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->addMember($_POST, $s))); + $fc->addRoute("GET", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->displayDeleteMember($s))); + $fc->addRoute("POST", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->deleteMember($_POST, $s))); +} + +//this is a global variable $basePath = get_public_path(); -$frontController = new FrontController($basePath); -$frontController->run(PhpSessionHandle::init()); +function run() { + global $basePath; + + $fc = new FrontController($basePath); + + initFrontController($fc); + $fc->run(PhpSessionHandle::init()); +} + + +run(); + diff --git a/src/Controller/Sub/AuthController.php b/src/Controller/AuthController.php similarity index 97% rename from src/Controller/Sub/AuthController.php rename to src/Controller/AuthController.php index 709be3e..0ee9a46 100644 --- a/src/Controller/Sub/AuthController.php +++ b/src/Controller/AuthController.php @@ -1,13 +1,12 @@ action = $action; + $this->isAuthRequired = $isAuthRequired; + } + + public function isAuthRequired(): bool { + return $this->isAuthRequired; + } + + /** + * @param mixed[] $params + * @param SessionHandle $session + * @return HttpResponse + * @throws Exception

+ * thrown if this action is required to be authenticated, but the given session does not contain a logged-in account. + *

+ *

+ * Caller is supposed to ensure that the user is logged-in before, if `$this->isAuthRequired()` is true before + * running this action. + *

+ */ + public function run(array $params, SessionHandle $session): HttpResponse { + $params = array_values($params); + if ($this->isAuthRequired) { + if ($session->getAccount() == null) { + throw new Exception("action requires authorization."); + } + } + + $params[] = $session; + return call_user_func_array($this->action, $params); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return Action an action that does not require to have an authorization. + */ + public static function noAuth(callable $action): Action { + return new Action($action, false); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return Action an action that does require to have an authorization. + */ + public static function auth(callable $action): Action { + return new Action($action, true); + } +} \ No newline at end of file diff --git a/src/Controller/FrontController.php b/src/Controller/Route/FrontController.php similarity index 61% rename from src/Controller/FrontController.php rename to src/Controller/Route/FrontController.php index d02bcbc..05b440e 100644 --- a/src/Controller/FrontController.php +++ b/src/Controller/Route/FrontController.php @@ -1,6 +1,6 @@ router = $this->createRouter($basePath); - $this->initializeRouterMap(); $this->basePath = $basePath; } + public function addRoute(string $method, string $path, Action $action): void { + $this->router->map($method, $path, $action); + } + /** * @param MutableSessionHandle $session * @return void @@ -61,85 +60,36 @@ class FrontController { return $router; } - /** - * Initialize project's routes - * - * @return void - */ - private function initializeRouterMap(): void { - $this->router->map("GET", "/home", self::USER_CONTROLLER); - $this->router->map("GET|POST", "/user/[a:action]/[i:idTactic]?", self::USER_CONTROLLER); - $this->router->map("GET|POST", "/visitor/[a:action]", self::VISITOR_CONTROLLER); - } - /** * @param array $match * @param MutableSessionHandle $session * @return void */ private function handleMatch(array $match, MutableSessionHandle $session): void { - $tag = $match['target']; - - $action = $this->getAction($match); - $params = $match["params"]; - unset($params['action']); - $this->handleResponseByType($this->tryToCall($tag, $action, array_values($params), $session)); + $action = $match['target']; + $params = array_values($match["params"]); + $this->handleResponseByType($this->tryToCall($action, $params, $session)); } /** - * @param string $controllerName - * @param string $action + * @param Action $action * @param array $params * @param MutableSessionHandle $session * @return HttpResponse */ - private function tryToCall(string $controllerName, string $action, array $params, MutableSessionHandle $session): HttpResponse { - if ($controllerName != self::VISITOR_CONTROLLER) { + private function tryToCall(Action $action, array $params, MutableSessionHandle $session): HttpResponse { + $account = null; + if ($action->isAuthRequired()) { $account = $session->getAccount(); if ($account == null) { // put in the session the initial url the user wanted to get $session->setInitialTarget($_SERVER['REQUEST_URI']); - return HttpResponse::redirect($this->basePath . "/visitor/login"); + return HttpResponse::redirect($this->basePath . "/login"); } } - $controller = $this->getController($controllerName); - - if (is_callable([$controller, $action])) { - // append the session as the last parameter of a controller function - $params[] = $session; - return call_user_func_array([$controller, $action], $params); - } else { - return ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], - ], HttpCodes::NOT_FOUND); - } - } - - /** - * Get the right method to call to do an action - * - * @param array $match - * @return string - */ - private function getAction(array $match): string { - if (isset($match["params"]["action"])) { - return $match["params"]["action"]; - } - return "home"; - } - - /** - * Initialize the right controller by the user's role - * - * @param string $controller - * @return mixed - */ - private function getController(string $controller) { - $namespace = "\\App\\Controller\\"; - $controller = $namespace . $controller; - return new $controller(); + return $action->run($params, $session); } /** @@ -182,7 +132,7 @@ class FrontController { $loader = new FilesystemLoader('../src/Views/'); $twig = new Environment($loader); $twig->display($file, $args); - } catch (RuntimeError | SyntaxError | LoaderError $e) { + } catch (RuntimeError|SyntaxError|LoaderError $e) { http_response_code(500); echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); throw $e; diff --git a/src/Controller/Sub/TeamController.php b/src/Controller/TeamController.php similarity index 68% rename from src/Controller/Sub/TeamController.php rename to src/Controller/TeamController.php index adefa41..6edb847 100644 --- a/src/Controller/Sub/TeamController.php +++ b/src/Controller/TeamController.php @@ -1,11 +1,12 @@ model = $model; } - public function displaySubmitTeam(): HttpResponse { + public function displayCreateTeam(SessionHandle $session): HttpResponse { return ViewHttpResponse::twig("insert_team.html.twig", []); } - public function displayAddMember() : HttpResponse { + public function displayAddMember(SessionHandle $session): HttpResponse { return ViewHttpResponse::twig("add_member.html.twig", []); } - public function displayDeleteMember() : HttpResponse { + public function displayDeleteMember(SessionHandle $session): HttpResponse { return ViewHttpResponse::twig("delete_member.html.twig", []); } - public function submitTeam(array $request): HttpResponse { + public function submitTeam(array $request, SessionHandle $session): HttpResponse { $errors = []; @@ -51,10 +52,10 @@ class TeamController { } return ViewHttpResponse::twig('insert_team.html.twig', ['bad_fields' => $badFields]); } - return $this->getTeam($this->model->createTeam($request['name'], $request['picture'], $request['mainColor'], $request['secondColor'])); + return $this->displayTeam($this->model->createTeam($request['name'], $request['picture'], $request['mainColor'], $request['secondColor']), $session); } - public function displayListTeamByName(): HttpResponse { + public function displayListTeamByName(SessionHandle $session): HttpResponse { return ViewHttpResponse::twig("list_team_by_name.html.twig", []); } @@ -62,7 +63,7 @@ class TeamController { * @param array $request * @return HttpResponse */ - public function listTeamByName(array $request): HttpResponse { + public function listTeamByName(array $request, SessionHandle $session): HttpResponse { $errors = []; $request = HttpRequest::from($request, $errors, [ "name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()], @@ -82,30 +83,30 @@ class TeamController { return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $results]); } - public function getTeam(int $id): HttpResponse { + public function displayTeam(int $id, SessionHandle $session): HttpResponse { $result = $this->model->displayTeam($id); return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]); } - public function addMember(array $request) : HttpResponse { + public function addMember(array $request, SessionHandle $session): HttpResponse { $errors = []; $request = HttpRequest::from($request, $errors, [ "team" => [Validators::isInteger()], - "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"),Validators::lenBetween(5, 256)] + "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)] ]); - return $this->getTeam($this->model->addMember($request['mail'], intval($request['team']), $request['role'])); + return $this->displayTeam($this->model->addMember($request['mail'], intval($request['team']), $request['role']), $session); } - public function deleteMember(array $request) : HttpResponse { + public function deleteMember(array $request, SessionHandle $session): HttpResponse { $errors = []; $request = HttpRequest::from($request, $errors, [ "team" => [Validators::isInteger()], - "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"),Validators::lenBetween(5, 256)] + "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)] ]); - return $this->getTeam($this->model->deleteMember($request['mail'], intval($request['team']))); + return $this->displayTeam($this->model->deleteMember($request['mail'], intval($request['team'])), $session); } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 51b6d97..3868aa4 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -3,86 +3,38 @@ namespace App\Controller; use App\Connexion; -use App\Controller\Sub\EditorController; use App\Gateway\TacticInfoGateway; -use App\Gateway\TeamGateway; use App\Http\HttpResponse; use App\Http\ViewHttpResponse; use App\Model\TacticModel; -use App\Model\TeamModel; use App\Session\SessionHandle; -class UserController extends VisitorController { - public function home(): HttpResponse { - $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - $listTactic = $model->getLast(5); - return ViewHttpResponse::twig("home.twig", ["recentTactic" => $listTactic]); - } - - public function settings(): HttpResponse { - return ViewHttpResponse::twig("account_settings.twig", []); - } - - public function view(int $id, SessionHandle $session): HttpResponse { - $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new Sub\VisualizerController($model))->visualize($id, $session); - } - - public function edit(int $id, SessionHandle $session): HttpResponse { - $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new EditorController($model))->edit($id, $session); - } +class UserController { - public function create(SessionHandle $session): HttpResponse { - $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new EditorController($model))->createNew($session); - } - - public function open(int $id, SessionHandle $session): HttpResponse { - $model = new TacticModel(new TacticInfoGateway(new Connexion(get_database()))); - return (new Sub\VisualizerController($model))->visualize($id, $session); - } - - public function createTeam(): HttpResponse { - $model = new TeamModel(new TeamGateway(new Connexion(get_database()))); - $ctrl = new Sub\TeamController($model); - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - return $ctrl->displaySubmitTeam(); - } - return $ctrl->submitTeam($_POST); - } + private TacticModel $tactics; - public function listTeams(): HttpResponse { - $model = new TeamModel(new TeamGateway(new Connexion(get_database()))); - $ctrl = new Sub\TeamController($model); - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - return $ctrl->displayListTeamByName(); - } - return $ctrl->listTeamByName($_POST); + /** + * @param TacticModel $tactics + */ + public function __construct(TacticModel $tactics) { + $this->tactics = $tactics; } - public function getTeam(int $id): HttpResponse { - $model = new TeamModel(new TeamGateway(new Connexion(get_database()))); - $ctrl = new Sub\TeamController($model); - return $ctrl->getTeam($id); - } - - public function addMember(): HttpResponse { - $model = new TeamModel(new TeamGateway(new Connexion(get_database()))); - $ctrl = new Sub\TeamController($model); - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - return $ctrl->displayAddMember($_POST); - } - return $ctrl->addMember($_POST); + /** + * @param SessionHandle $session + * @return HttpResponse the home page + */ + public function home(SessionHandle $session): HttpResponse { + //TODO use session's account to get the last 5 tactics if the logged-in account + $listTactic = $this->tactics->getLast(5); + return ViewHttpResponse::twig("home.twig", ["recentTactic" => $listTactic]); } - public function deleteMember(): HttpResponse { - $model = new TeamModel(new TeamGateway(new Connexion(get_database()))); - $ctrl = new Sub\TeamController($model); - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - return $ctrl->displayDeleteMember($_POST); - } - return $ctrl->deleteMember($_POST); + /** + * @return HttpResponse account settings page + */ + public function settings(SessionHandle $session): HttpResponse { + return ViewHttpResponse::twig("account_settings.twig", []); } -} +} \ No newline at end of file diff --git a/src/Controller/VisitorController.php b/src/Controller/VisitorController.php deleted file mode 100644 index e74934a..0000000 --- a/src/Controller/VisitorController.php +++ /dev/null @@ -1,28 +0,0 @@ -displayRegister(); - } - return (new Sub\AuthController($model))->confirmRegister($_POST, $session); - } - - final public function login(MutableSessionHandle $session): HttpResponse { - $model = new AuthModel(new AccountGateway(new Connexion(get_database()))); - if ($_SERVER['REQUEST_METHOD'] === 'GET') { - return (new Sub\AuthController($model))->displayLogin(); - } - return (new Sub\AuthController($model))->confirmLogin($_POST, $session); - } - -} diff --git a/src/Controller/Sub/VisualizerController.php b/src/Controller/VisualizerController.php similarity index 90% rename from src/Controller/Sub/VisualizerController.php rename to src/Controller/VisualizerController.php index c7e5098..9b971c6 100644 --- a/src/Controller/Sub/VisualizerController.php +++ b/src/Controller/VisualizerController.php @@ -1,14 +1,12 @@ tacticModel = $tacticModel; } diff --git a/src/Data/Color.php b/src/Data/Color.php index b12e27a..b2da83b 100755 --- a/src/Data/Color.php +++ b/src/Data/Color.php @@ -16,9 +16,6 @@ class Color { */ private function __construct(string $value) { - if ($value < 0 || $value > 0xFFFFFF) { - throw new InvalidArgumentException("int color value is invalid, must be positive and lower than 0xFFFFFF"); - } $this->hex = $value; } diff --git a/src/Views/add_member.html.twig b/src/Views/add_member.html.twig index ef74da2..6c5a3e3 100644 --- a/src/Views/add_member.html.twig +++ b/src/Views/add_member.html.twig @@ -73,7 +73,7 @@

Ajouter un membre à votre équipe

- +
diff --git a/src/Views/delete_member.html.twig b/src/Views/delete_member.html.twig index a478284..b7d0d3b 100644 --- a/src/Views/delete_member.html.twig +++ b/src/Views/delete_member.html.twig @@ -56,7 +56,7 @@

Supprimez un membre de votre équipe

- +
diff --git a/src/Views/display_teams.html.twig b/src/Views/display_teams.html.twig index bf89909..e4da64e 100644 --- a/src/Views/display_teams.html.twig +++ b/src/Views/display_teams.html.twig @@ -10,7 +10,7 @@

Aucune équipe n'a été trouvée

Chercher une équipe

- +
@@ -22,7 +22,7 @@
{% else %} {% for t in teams %} -
+

Nom de l'équipe : {{ t.name }}

logo de l'équipe
diff --git a/src/Views/home.twig b/src/Views/home.twig index 35379ce..29d228a 100644 --- a/src/Views/home.twig +++ b/src/Views/home.twig @@ -52,9 +52,9 @@

IQ Ball

-
+
Account logo

Mon profil

@@ -63,7 +63,7 @@

Mes équipes

- + {% if recentTeam != null %} {% for team in recentTeam %} @@ -77,13 +77,13 @@

Mes strategies

- + {% if recentTactic != null %} {% for tactic in recentTactic %} -
+

{{tactic.id}} - {{tactic.name}} - {{tactic.creation_date}}

- +
{% endfor %} {% else %} diff --git a/src/Views/insert_team.html.twig b/src/Views/insert_team.html.twig index 0fc59ca..0cc85dd 100644 --- a/src/Views/insert_team.html.twig +++ b/src/Views/insert_team.html.twig @@ -64,7 +64,7 @@

Créer une équipe

- +
diff --git a/src/Views/list_team_by_name.html.twig b/src/Views/list_team_by_name.html.twig index 0ea1905..09768e6 100644 --- a/src/Views/list_team_by_name.html.twig +++ b/src/Views/list_team_by_name.html.twig @@ -62,7 +62,7 @@

Chercher une équipe

- +
From de75577f3dc88c2600a089445b8969f40f17ce56 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Fri, 24 Nov 2023 10:20:45 +0100 Subject: [PATCH 35/38] divide src in three subdirs, exploded FrontController into index.php and api/index.php --- composer.json | 2 +- phpstan.neon | 5 +- public/api/index.php | 127 ++++--------- public/index.php | 179 ++++++++++++------ .../Api/APIAuthController.php | 16 +- .../Api/APITacticController.php | 20 +- src/Api/ApiAction.php | 46 +++++ src/App/AppAction.php | 55 ++++++ src/{ => App}/Controller/AuthController.php | 22 +-- src/{ => App}/Controller/EditorController.php | 18 +- src/{ => App}/Controller/TeamController.php | 45 +++-- src/{ => App}/Controller/UserController.php | 15 +- .../Controller/VisualizerController.php | 16 +- src/{ => App}/Views/account_settings.twig | 0 src/{ => App}/Views/add_member.html.twig | 0 src/{ => App}/Views/delete_member.html.twig | 0 .../Views/display_auth_confirm.html.twig | 0 src/{ => App}/Views/display_login.html.twig | 0 .../Views/display_register.html.twig | 0 src/{ => App}/Views/display_results.html.twig | 0 src/{ => App}/Views/display_team.html.twig | 0 src/{ => App}/Views/display_teams.html.twig | 0 src/{ => App}/Views/error.html.twig | 0 src/{ => App}/Views/home.twig | 2 +- src/{ => App}/Views/insert_team.html.twig | 0 .../Views/list_team_by_name.html.twig | 0 src/{ => App}/react-display-file.php | 0 src/{ => App}/react-display.php | 0 src/Controller/Route/Action.php | 71 ------- src/Controller/Route/FrontController.php | 143 -------------- src/{Connexion.php => Core/Connection.php} | 4 +- src/{ => Core}/Data/Account.php | 2 +- src/{ => Core}/Data/AccountUser.php | 2 +- src/{ => Core}/Data/Color.php | 2 +- src/{ => Core}/Data/Member.php | 7 +- src/{ => Core}/Data/MemberRole.php | 4 +- src/{ => Core}/Data/TacticInfo.php | 2 +- src/{ => Core}/Data/Team.php | 4 +- src/{ => Core}/Data/User.php | 2 +- src/{ => Core}/Gateway/AccountGateway.php | 12 +- src/{ => Core}/Gateway/AuthGateway.php | 10 +- src/{ => Core}/Gateway/TacticInfoGateway.php | 14 +- src/{ => Core}/Gateway/TeamGateway.php | 48 +++-- src/{ => Core}/Http/HttpCodes.php | 2 +- src/{ => Core}/Http/HttpRequest.php | 10 +- src/{ => Core}/Http/HttpResponse.php | 2 +- src/{ => Core}/Http/JsonHttpResponse.php | 2 +- src/{ => Core}/Http/ViewHttpResponse.php | 2 +- src/{ => Core}/Model/AuthModel.php | 10 +- src/{ => Core}/Model/TacticModel.php | 11 +- src/{ => Core}/Model/TeamModel.php | 34 ++-- src/Core/Route/AbstractAction.php | 36 ++++ src/{Controller => Core/Route}/Control.php | 18 +- .../Session/MutableSessionHandle.php | 4 +- src/{ => Core}/Session/PhpSessionHandle.php | 4 +- src/{ => Core}/Session/SessionHandle.php | 4 +- .../Validation/ComposedValidator.php | 2 +- .../Validation/FieldValidationFail.php | 2 +- .../Validation/FunctionValidator.php | 2 +- .../Validation/SimpleFunctionValidator.php | 2 +- src/{ => Core}/Validation/Validation.php | 2 +- src/{ => Core}/Validation/ValidationFail.php | 2 +- src/{ => Core}/Validation/Validator.php | 2 +- src/{ => Core}/Validation/Validators.php | 2 +- src/{ => Core}/Validator/TacticValidator.php | 6 +- {public => src}/utils.php | 0 66 files changed, 501 insertions(+), 555 deletions(-) rename src/{Controller => }/Api/APIAuthController.php (77%) rename src/{Controller => }/Api/APITacticController.php (85%) create mode 100644 src/Api/ApiAction.php create mode 100644 src/App/AppAction.php rename src/{ => App}/Controller/AuthController.php (86%) rename src/{ => App}/Controller/EditorController.php (80%) rename src/{ => App}/Controller/TeamController.php (79%) rename src/{ => App}/Controller/UserController.php (80%) rename src/{ => App}/Controller/VisualizerController.php (74%) rename src/{ => App}/Views/account_settings.twig (100%) rename src/{ => App}/Views/add_member.html.twig (100%) rename src/{ => App}/Views/delete_member.html.twig (100%) rename src/{ => App}/Views/display_auth_confirm.html.twig (100%) rename src/{ => App}/Views/display_login.html.twig (100%) rename src/{ => App}/Views/display_register.html.twig (100%) rename src/{ => App}/Views/display_results.html.twig (100%) rename src/{ => App}/Views/display_team.html.twig (100%) rename src/{ => App}/Views/display_teams.html.twig (100%) rename src/{ => App}/Views/error.html.twig (100%) rename src/{ => App}/Views/home.twig (97%) rename src/{ => App}/Views/insert_team.html.twig (100%) rename src/{ => App}/Views/list_team_by_name.html.twig (100%) rename src/{ => App}/react-display-file.php (100%) rename src/{ => App}/react-display.php (100%) delete mode 100644 src/Controller/Route/Action.php delete mode 100644 src/Controller/Route/FrontController.php rename src/{Connexion.php => Core/Connection.php} (97%) rename src/{ => Core}/Data/Account.php (97%) rename src/{ => Core}/Data/AccountUser.php (97%) rename src/{ => Core}/Data/Color.php (97%) rename src/{ => Core}/Data/Member.php (88%) rename src/{ => Core}/Data/MemberRole.php (94%) rename src/{ => Core}/Data/TacticInfo.php (97%) rename src/{ => Core}/Data/Team.php (95%) rename src/{ => Core}/Data/User.php (93%) rename src/{ => Core}/Gateway/AccountGateway.php (90%) rename src/{ => Core}/Gateway/AuthGateway.php (86%) rename src/{ => Core}/Gateway/TacticInfoGateway.php (87%) rename src/{ => Core}/Gateway/TeamGateway.php (68%) rename src/{ => Core}/Http/HttpCodes.php (90%) rename src/{ => Core}/Http/HttpRequest.php (92%) rename src/{ => Core}/Http/HttpResponse.php (97%) rename src/{ => Core}/Http/JsonHttpResponse.php (95%) rename src/{ => Core}/Http/ViewHttpResponse.php (98%) rename src/{ => Core}/Model/AuthModel.php (91%) rename src/{ => Core}/Model/TacticModel.php (89%) rename src/{ => Core}/Model/TeamModel.php (72%) create mode 100644 src/Core/Route/AbstractAction.php rename src/{Controller => Core/Route}/Control.php (90%) rename src/{ => Core}/Session/MutableSessionHandle.php (86%) rename src/{ => Core}/Session/PhpSessionHandle.php (92%) rename src/{ => Core}/Session/SessionHandle.php (89%) rename src/{ => Core}/Validation/ComposedValidator.php (94%) rename src/{ => Core}/Validation/FieldValidationFail.php (97%) rename src/{ => Core}/Validation/FunctionValidator.php (94%) rename src/{ => Core}/Validation/SimpleFunctionValidator.php (96%) rename src/{ => Core}/Validation/Validation.php (96%) rename src/{ => Core}/Validation/ValidationFail.php (96%) rename src/{ => Core}/Validation/Validator.php (95%) rename src/{ => Core}/Validation/Validators.php (98%) rename src/{ => Core}/Validator/TacticValidator.php (80%) rename {public => src}/utils.php (100%) diff --git a/composer.json b/composer.json index a3c0e4b..1d3a4d7 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "autoload": { "psr-4": { - "App\\": "src/" + "IQBall\\": "src/" } }, "require": { diff --git a/phpstan.neon b/phpstan.neon index 715fe84..346baaa 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,13 +3,10 @@ parameters: level: 6 paths: - src - - public scanFiles: - config.php - sql/database.php - profiles/dev-config-profile.php - profiles/prod-config-profile.php - - public/api/index.php excludePaths: - - src/react-display-file.php - - public/api/index.php + - src/App/react-display-file.php diff --git a/public/api/index.php b/public/api/index.php index cb278df..eeac0c1 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -5,84 +5,39 @@ require "../../vendor/autoload.php"; require "../../sql/database.php"; require "../utils.php"; -use App\Connexion; -use App\Controller\Api\APIAuthController; -use App\Controller\Api\APITacticController; -use App\Data\Account; -use App\Gateway\AccountGateway; -use App\Gateway\TacticInfoGateway; -use App\Http\HttpResponse; -use App\Http\JsonHttpResponse; -use App\Http\ViewHttpResponse; -use App\Model\AuthModel; -use App\Model\TacticModel; -use App\Session\PhpSessionHandle; -use App\Validation\ValidationFail; +use IQBall\Api\ApiAction; +use IQBall\Core\Connection; +use IQBall\Api\APIAuthController; +use IQBall\Api\APITacticController; +use IQBall\Core\Data\Account; +use IQBall\Core\Gateway\AccountGateway; +use IQBall\Core\Gateway\TacticInfoGateway; +use IQBall\Core\Http\HttpResponse; +use IQBall\Core\Http\JsonHttpResponse; +use IQBall\Core\Http\ViewHttpResponse; +use IQBall\Core\Model\AuthModel; +use IQBall\Core\Model\TacticModel; +use IQBall\Core\Session\PhpSessionHandle; +use IQBall\Core\Validation\ValidationFail; function getTacticController(): APITacticController { - return new APITacticController(new TacticModel(new TacticInfoGateway(new Connexion(get_database())))); + return new APITacticController(new TacticModel(new TacticInfoGateway(new Connection(get_database())))); } function getAuthController(): APIAuthController { - return new APIAuthController(new AuthModel(new AccountGateway(new Connexion(get_database())))); + return new APIAuthController(new AuthModel(new AccountGateway(new Connection(get_database())))); } -/** - * A Front controller action - */ -//TODO workaround for generic Action -class ApiAction { - /** - * @var callable(mixed[]): HttpResponse $action action to call - */ - private $action; - - private bool $isAuthRequired; - - /** - * @param callable(mixed[]): HttpResponse $action - */ - private function __construct(callable $action, bool $isAuthRequired) { - $this->action = $action; - $this->isAuthRequired = $isAuthRequired; - } +function getRoutes(): AltoRouter { + $router = new AltoRouter(); + $router->setBasePath(get_public_path() . "/api"); - public function isAuthRequired(): bool { - return $this->isAuthRequired; - } + $router->map("POST", "/tactic/[i:id]/edit/name", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); + $router->map("GET", "/tactic/[i:id]", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc))); + $router->map("POST", "/tactic/new", ApiAction::auth(fn(Account $acc) => getTacticController()->newTactic($acc))); + $router->map("POST", "/auth", ApiAction::noAuth(fn() => getAuthController()->authorize())); - /** - * @param mixed[] $params - * @param ?Account $account - * @return HttpResponse - */ - public function run(array $params, ?Account $account): HttpResponse { - $params = array_values($params); - if ($this->isAuthRequired) { - if ($account == null) { - throw new Exception("action requires authorization."); - } - $params[] = $account; - } - - return call_user_func_array($this->action, $params); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return ApiAction an action that does not require to have an authorization. - */ - public static function noAuth(callable $action): ApiAction { - return new ApiAction($action, false); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return ApiAction an action that does require to have an authorization. - */ - public static function auth(callable $action): ApiAction { - return new ApiAction($action, true); - } + return $router; } /** @@ -97,7 +52,7 @@ function handleMatch(array $match): HttpResponse { $action = $match['target']; if (!$action instanceof ApiAction) { - throw new Exception("routed action is not an Action object."); + throw new Exception("routed action is not an AppAction object."); } $auth = null; @@ -122,27 +77,23 @@ function tryGetAuthorization(): ?Account { } $token = $headers['Authorization']; - $gateway = new AccountGateway(new Connexion(get_database())); + $gateway = new AccountGateway(new Connection(get_database())); return $gateway->getAccountFromToken($token); } -$router = new AltoRouter(); -$router->setBasePath(get_public_path() . "/api"); - -$router->map("POST", "/tactic/[i:id]/edit/name", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); -$router->map("GET", "/tactic/[i:id]", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc))); -$router->map("POST", "/tactic/new", ApiAction::auth(fn(Account $acc) => getTacticController()->newTactic($acc))); -$router->map("POST", "/auth", ApiAction::noAuth(fn() => getAuthController()->authorize())); - -$match = $router->match(); +function render(HttpResponse $response): void { + http_response_code($response->getCode()); + foreach ($response->getHeaders() as $header => $value) { + header("$header: $value"); + } -$response = handleMatch($match); -http_response_code($response->getCode()); - -if ($response instanceof JsonHttpResponse) { - header('Content-type: application/json'); - echo $response->getJson(); -} elseif ($response instanceof ViewHttpResponse) { - throw new Exception("API returned a view http response."); + if ($response instanceof JsonHttpResponse) { + header('Content-type: application/json'); + echo $response->getJson(); + } elseif ($response instanceof ViewHttpResponse) { + throw new Exception("API returned a view http response."); + } } + +render(handleMatch(getRoutes()->match())); diff --git a/public/index.php b/public/index.php index 8d1f148..07d58c1 100644 --- a/public/index.php +++ b/public/index.php @@ -4,96 +4,157 @@ require "../vendor/autoload.php"; require "../config.php"; require "../sql/database.php"; -require "utils.php"; -require "../src/react-display.php"; - -use App\Controller\AuthController; -use App\Controller\EditorController; -use App\Controller\Route\Action; -use App\Controller\Route\FrontController; -use App\Controller\TeamController; -use App\Controller\UserController; -use App\Controller\VisualizerController; -use App\Gateway\AccountGateway; -use App\Gateway\TacticInfoGateway; -use App\Gateway\TeamGateway; -use App\Model\AuthModel; -use App\Model\TacticModel; -use App\Model\TeamModel; -use App\Session\MutableSessionHandle; -use App\Session\PhpSessionHandle; -use App\Connexion; -use App\Session\SessionHandle; - -$connexion = new Connexion(get_database()); +require "../src/utils.php"; +require "../src/App/react-display.php"; + +use IQBall\App\AppAction; +use IQBall\App\Controller\AuthController; +use IQBall\App\Controller\EditorController; +use IQBall\App\Controller\TeamController; +use IQBall\App\Controller\UserController; +use IQBall\App\Controller\VisualizerController; +use IQBall\Core\Connection; +use IQBall\Core\Gateway\AccountGateway; +use IQBall\Core\Gateway\TacticInfoGateway; +use IQBall\Core\Gateway\TeamGateway; +use IQBall\Core\Http\HttpCodes; +use IQBall\Core\Http\HttpResponse; +use IQBall\Core\Http\JsonHttpResponse; +use IQBall\Core\Http\ViewHttpResponse; +use IQBall\Core\Model\AuthModel; +use IQBall\Core\Model\TacticModel; +use IQBall\Core\Model\TeamModel; +use IQBall\Core\Session\MutableSessionHandle; +use IQBall\Core\Session\PhpSessionHandle; +use IQBall\Core\Session\SessionHandle; +use IQBall\Core\Validation\ValidationFail; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; +use Twig\Loader\FilesystemLoader; + +function getConnection(): Connection { + return new Connection(get_database()); +} function getUserController(): UserController { - global $connexion; - return new UserController(new TacticModel(new TacticInfoGateway($connexion))); + return new UserController(new TacticModel(new TacticInfoGateway(getConnection()))); } function getVisualizerController(): VisualizerController { - global $connexion; - return new VisualizerController(new TacticModel(new TacticInfoGateway($connexion))); + return new VisualizerController(new TacticModel(new TacticInfoGateway(getConnection()))); } function getEditorController(): EditorController { - global $connexion; - return new EditorController(new TacticModel(new TacticInfoGateway($connexion))); + return new EditorController(new TacticModel(new TacticInfoGateway(getConnection()))); } function getTeamController(): TeamController { - global $connexion; - return new TeamController(new TeamModel(new TeamGateway($connexion))); + return new TeamController(new TeamModel(new TeamGateway(getConnection()))); } function getAuthController(): AuthController { - global $connexion; - return new AuthController(new AuthModel(new AccountGateway($connexion))); + return new AuthController(new AuthModel(new AccountGateway(getConnection()))); } -function initFrontController(FrontController $fc) { +function getRoutes(): AltoRouter { + global $basePath; + + $ar = new AltoRouter(); + $ar->setBasePath($basePath); + //authentication - $fc->addRoute("GET", "/login", Action::noAuth(fn() => getAuthController()->displayLogin())); - $fc->addRoute("GET", "/register", Action::noAuth(fn() => getAuthController()->displayRegister())); - $fc->addRoute("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s))); - $fc->addRoute("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s))); + $ar->map("GET", "/login", AppAction::noAuth(fn() => getAuthController()->displayLogin())); + $ar->map("GET", "/register", AppAction::noAuth(fn() => getAuthController()->displayRegister())); + $ar->map("POST", "/login", AppAction::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s))); + $ar->map("POST", "/register", AppAction::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s))); //user-related - $fc->addRoute("GET", "/home", Action::auth(fn(SessionHandle $s) => getUserController()->home($s))); - $fc->addRoute("GET", "/settings", Action::auth(fn(SessionHandle $s) => getUserController()->settings($s))); + $ar->map("GET", "/home", AppAction::auth(fn(SessionHandle $s) => getUserController()->home($s))); + $ar->map("GET", "/settings", AppAction::auth(fn(SessionHandle $s) => getUserController()->settings($s))); //tactic-related - $fc->addRoute("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s))); - $fc->addRoute("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s))); - $fc->addRoute("GET", "/tactic/new", Action::auth(fn(SessionHandle $s) => getEditorController()->createNew($s))); + $ar->map("GET", "/tactic/[i:id]/view", AppAction::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s))); + $ar->map("GET", "/tactic/[i:id]/edit", AppAction::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s))); + $ar->map("GET", "/tactic/new", AppAction::auth(fn(SessionHandle $s) => getEditorController()->createNew($s))); //team-related - $fc->addRoute("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); - $fc->addRoute("POST", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->submitTeam($_POST, $s))); - $fc->addRoute("GET", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->displayListTeamByName($s))); - $fc->addRoute("POST", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->listTeamByName($_POST, $s))); - $fc->addRoute("GET", "/team/[i:id]", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->displayTeam($id, $s))); - $fc->addRoute("GET", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->displayAddMember($s))); - $fc->addRoute("POST", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->addMember($_POST, $s))); - $fc->addRoute("GET", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->displayDeleteMember($s))); - $fc->addRoute("POST", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->deleteMember($_POST, $s))); + $ar->map("GET", "/team/new", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); + $ar->map("POST", "/team/new", AppAction::auth(fn(SessionHandle $s) => getTeamController()->submitTeam($_POST, $s))); + $ar->map("GET", "/team/search", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayListTeamByName($s))); + $ar->map("POST", "/team/search", AppAction::auth(fn(SessionHandle $s) => getTeamController()->listTeamByName($_POST, $s))); + $ar->map("GET", "/team/[i:id]", AppAction::auth(fn(int $id, SessionHandle $s) => getTeamController()->displayTeam($id, $s))); + $ar->map("GET", "/team/members/add", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayAddMember($s))); + $ar->map("POST", "/team/members/add", AppAction::auth(fn(SessionHandle $s) => getTeamController()->addMember($_POST, $s))); + $ar->map("GET", "/team/members/remove", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayDeleteMember($s))); + $ar->map("POST", "/team/members/remove", AppAction::auth(fn(SessionHandle $s) => getTeamController()->deleteMember($_POST, $s))); + + return $ar; } -//this is a global variable -$basePath = get_public_path(); +function render(HttpResponse $response): void { + http_response_code($response->getCode()); + foreach ($response->getHeaders() as $header => $value) { + header("$header: $value"); + } -function run() { - global $basePath; + if ($response instanceof ViewHttpResponse) { + renderView($response); + } elseif ($response instanceof JsonHttpResponse) { + header('Content-type: application/json'); + echo $response->getJson(); + } +} - $fc = new FrontController($basePath); +function renderView(ViewHttpResponse $response): void { + $file = $response->getFile(); + $args = $response->getArguments(); + + switch ($response->getViewKind()) { + case ViewHttpResponse::REACT_VIEW: + send_react_front($file, $args); + break; + case ViewHttpResponse::TWIG_VIEW: + try { + $loader = new FilesystemLoader('../src/Views/'); + $twig = new Environment($loader); + $twig->display($file, $args); + } catch (RuntimeError | SyntaxError | LoaderError $e) { + http_response_code(500); + echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); + throw $e; + } + break; + } +} - initFrontController($fc); - $fc->run(PhpSessionHandle::init()); +function runAction(AppAction $action, array $params, MutableSessionHandle $session): HttpResponse { + global $basePath; + if ($action->isAuthRequired()) { + $account = $session->getAccount(); + if ($account == null) { + // put in the session the initial url the user wanted to get + $session->setInitialTarget($_SERVER['REQUEST_URI']); + return HttpResponse::redirect($basePath . "/login"); + } + } + + return $action->run($params, $session); } +function runMatch(array $match, MutableSessionHandle $session): HttpResponse { + if (!$match) { + return ViewHttpResponse::twig("error.html.twig", [ + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], + ], HttpCodes::NOT_FOUND); + } -run(); + return runAction($match['target'], $match['params'], $session); +} +//this is a global variable +$basePath = get_public_path(); +render(runMatch(getRoutes()->match(), PhpSessionHandle::init())); diff --git a/src/Controller/Api/APIAuthController.php b/src/Api/APIAuthController.php similarity index 77% rename from src/Controller/Api/APIAuthController.php rename to src/Api/APIAuthController.php index d9e4c23..c4f5976 100644 --- a/src/Controller/Api/APIAuthController.php +++ b/src/Api/APIAuthController.php @@ -1,13 +1,13 @@ + */ +class ApiAction extends AbstractAction { + + /** + * @param mixed[] $params + * @param ?Account $session + * @return HttpResponse + */ + public function run(array $params, $session): HttpResponse { + $params = array_values($params); + if ($this->isAuthRequired()) { + if ($session == null) { + throw new \Exception("action requires authorization."); + } + $params[] = $session; + } + + return call_user_func_array($this->action, $params); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return ApiAction an action that does not require to have an authorization. + */ + public static function noAuth(callable $action): ApiAction { + return new ApiAction($action, false); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return ApiAction an action that does require to have an authorization. + */ + public static function auth(callable $action): ApiAction { + return new ApiAction($action, true); + } +} \ No newline at end of file diff --git a/src/App/AppAction.php b/src/App/AppAction.php new file mode 100644 index 0000000..0e4dcdf --- /dev/null +++ b/src/App/AppAction.php @@ -0,0 +1,55 @@ + + */ +class AppAction extends AbstractAction { + + /** + * @param mixed[] $params + * @param MutableSessionHandle $session + * @return HttpResponse + * @throws Exception

+ * thrown if this action is required to be authenticated, but the given session does not contain a logged-in account. + *

+ *

+ * Caller is supposed to ensure that the user is logged-in before, if `$this->isAuthRequired()` is true before + * running this action. + *

+ */ + public function run(array $params, $session): HttpResponse { + $params = array_values($params); + if ($this->isAuthRequired()) { + if ($session->getAccount() == null) { + throw new Exception("action requires authorization."); + } + } + + $params[] = $session; + return call_user_func_array($this->action, $params); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return AppAction an action that does not require to have an authorization. + */ + public static function noAuth(callable $action): AppAction { + return new AppAction($action, false); + } + + /** + * @param callable(mixed[]): HttpResponse $action + * @return AppAction an action that does require to have an authorization. + */ + public static function auth(callable $action): AppAction { + return new AppAction($action, true); + } +} diff --git a/src/Controller/AuthController.php b/src/App/Controller/AuthController.php similarity index 86% rename from src/Controller/AuthController.php rename to src/App/Controller/AuthController.php index 0ee9a46..f74e45d 100644 --- a/src/Controller/AuthController.php +++ b/src/App/Controller/AuthController.php @@ -1,14 +1,14 @@ $fails]); } @@ -44,7 +44,7 @@ class AuthController { "username" => [Validators::name(), Validators::lenBetween(2, 32)], "password" => [Validators::lenBetween(6, 256)], "confirmpassword" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/","invalide"),Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/", "invalide"),Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_register.html.twig", $fails); @@ -73,7 +73,7 @@ class AuthController { $fails = []; $request = HttpRequest::from($request, $fails, [ "password" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/","invalide"),Validators::lenBetween(5, 256)], + "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/", "invalide"),Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { return $this->displayBadFields("display_login.html.twig", $fails); diff --git a/src/Controller/EditorController.php b/src/App/Controller/EditorController.php similarity index 80% rename from src/Controller/EditorController.php rename to src/App/Controller/EditorController.php index bfe80b0..0b47cba 100644 --- a/src/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -1,14 +1,14 @@ $request + * @param SessionHandle $session + * @return HttpResponse + */ public function submitTeam(array $request, SessionHandle $session): HttpResponse { - - $errors = []; - - $request = HttpRequest::from($request, $errors, [ + $failures = []; + $request = HttpRequest::from($request, $failures, [ "name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()], "mainColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')], "secondColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')], "picture" => [Validators::isURL()], ]); - if (!empty($errors)) { + if (!empty($failures)) { $badFields = []; - foreach ($errors as $e) { + foreach ($failures as $e) { if ($e instanceof FieldValidationFail) { $badFields[] = $e->getFieldName(); } @@ -88,23 +91,33 @@ class TeamController { return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]); } + /** + * @param array $request + * @param SessionHandle $session + * @return HttpResponse + */ public function addMember(array $request, SessionHandle $session): HttpResponse { $errors = []; $request = HttpRequest::from($request, $errors, [ "team" => [Validators::isInteger()], - "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)] + "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], ]); return $this->displayTeam($this->model->addMember($request['mail'], intval($request['team']), $request['role']), $session); } + /** + * @param array $request + * @param SessionHandle $session + * @return HttpResponse + */ public function deleteMember(array $request, SessionHandle $session): HttpResponse { $errors = []; $request = HttpRequest::from($request, $errors, [ "team" => [Validators::isInteger()], - "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)] + "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], ]); return $this->displayTeam($this->model->deleteMember($request['mail'], intval($request['team'])), $session); diff --git a/src/Controller/UserController.php b/src/App/Controller/UserController.php similarity index 80% rename from src/Controller/UserController.php rename to src/App/Controller/UserController.php index 3868aa4..5d45038 100644 --- a/src/Controller/UserController.php +++ b/src/App/Controller/UserController.php @@ -1,16 +1,13 @@ IQ Ball

Account logo

Mon profil

diff --git a/src/Views/insert_team.html.twig b/src/App/Views/insert_team.html.twig similarity index 100% rename from src/Views/insert_team.html.twig rename to src/App/Views/insert_team.html.twig diff --git a/src/Views/list_team_by_name.html.twig b/src/App/Views/list_team_by_name.html.twig similarity index 100% rename from src/Views/list_team_by_name.html.twig rename to src/App/Views/list_team_by_name.html.twig diff --git a/src/react-display-file.php b/src/App/react-display-file.php similarity index 100% rename from src/react-display-file.php rename to src/App/react-display-file.php diff --git a/src/react-display.php b/src/App/react-display.php similarity index 100% rename from src/react-display.php rename to src/App/react-display.php diff --git a/src/Controller/Route/Action.php b/src/Controller/Route/Action.php deleted file mode 100644 index 61fd13a..0000000 --- a/src/Controller/Route/Action.php +++ /dev/null @@ -1,71 +0,0 @@ -action = $action; - $this->isAuthRequired = $isAuthRequired; - } - - public function isAuthRequired(): bool { - return $this->isAuthRequired; - } - - /** - * @param mixed[] $params - * @param SessionHandle $session - * @return HttpResponse - * @throws Exception

- * thrown if this action is required to be authenticated, but the given session does not contain a logged-in account. - *

- *

- * Caller is supposed to ensure that the user is logged-in before, if `$this->isAuthRequired()` is true before - * running this action. - *

- */ - public function run(array $params, SessionHandle $session): HttpResponse { - $params = array_values($params); - if ($this->isAuthRequired) { - if ($session->getAccount() == null) { - throw new Exception("action requires authorization."); - } - } - - $params[] = $session; - return call_user_func_array($this->action, $params); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return Action an action that does not require to have an authorization. - */ - public static function noAuth(callable $action): Action { - return new Action($action, false); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return Action an action that does require to have an authorization. - */ - public static function auth(callable $action): Action { - return new Action($action, true); - } -} \ No newline at end of file diff --git a/src/Controller/Route/FrontController.php b/src/Controller/Route/FrontController.php deleted file mode 100644 index 05b440e..0000000 --- a/src/Controller/Route/FrontController.php +++ /dev/null @@ -1,143 +0,0 @@ -router = $this->createRouter($basePath); - $this->basePath = $basePath; - } - - public function addRoute(string $method, string $path, Action $action): void { - $this->router->map($method, $path, $action); - } - - /** - * @param MutableSessionHandle $session - * @return void - * @throws LoaderError - * @throws RuntimeError - * @throws SyntaxError - */ - public function run(MutableSessionHandle $session): void { - $match = $this->router->match(); - if ($match) { - $this->handleMatch($match, $session); - return; - } - - $this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], - ], HttpCodes::NOT_FOUND)); - } - - /** - * Create a new instance of an AltoRouter - * - * @param string $basePath - * @return AltoRouter - */ - public function createRouter(string $basePath): AltoRouter { - $router = new AltoRouter(); - $router->setBasePath($basePath); - return $router; - } - - /** - * @param array $match - * @param MutableSessionHandle $session - * @return void - */ - private function handleMatch(array $match, MutableSessionHandle $session): void { - $action = $match['target']; - $params = array_values($match["params"]); - $this->handleResponseByType($this->tryToCall($action, $params, $session)); - } - - - /** - * @param Action $action - * @param array $params - * @param MutableSessionHandle $session - * @return HttpResponse - */ - private function tryToCall(Action $action, array $params, MutableSessionHandle $session): HttpResponse { - $account = null; - if ($action->isAuthRequired()) { - $account = $session->getAccount(); - if ($account == null) { - // put in the session the initial url the user wanted to get - $session->setInitialTarget($_SERVER['REQUEST_URI']); - return HttpResponse::redirect($this->basePath . "/login"); - } - } - - return $action->run($params, $session); - } - - /** - * Redirect the return by the response's type - * - * @param HttpResponse $response - * @return void - */ - private function handleResponseByType(HttpResponse $response): void { - http_response_code($response->getCode()); - - foreach ($response->getHeaders() as $header => $value) { - header("$header: $value"); - } - - if ($response instanceof ViewHttpResponse) { - $this->displayViewByKind($response); - } elseif ($response instanceof JsonHttpResponse) { - header('Content-type: application/json'); - echo $response->getJson(); - } - } - - /** - * Use the right method to display the response - * - * @param ViewHttpResponse $response - * @return void - */ - private function displayViewByKind(ViewHttpResponse $response): void { - $file = $response->getFile(); - $args = $response->getArguments(); - - switch ($response->getViewKind()) { - case ViewHttpResponse::REACT_VIEW: - send_react_front($file, $args); - break; - case ViewHttpResponse::TWIG_VIEW: - try { - $loader = new FilesystemLoader('../src/Views/'); - $twig = new Environment($loader); - $twig->display($file, $args); - } catch (RuntimeError|SyntaxError|LoaderError $e) { - http_response_code(500); - echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); - throw $e; - } - break; - } - } -} diff --git a/src/Connexion.php b/src/Core/Connection.php similarity index 97% rename from src/Connexion.php rename to src/Core/Connection.php index 987d35b..019b515 100644 --- a/src/Connexion.php +++ b/src/Core/Connection.php @@ -1,10 +1,10 @@ teamId; } } diff --git a/src/Data/MemberRole.php b/src/Core/Data/MemberRole.php similarity index 94% rename from src/Data/MemberRole.php rename to src/Core/Data/MemberRole.php index 559d516..7b63064 100755 --- a/src/Data/MemberRole.php +++ b/src/Core/Data/MemberRole.php @@ -1,8 +1,8 @@ members; } - public function addMember(Member $m) { + public function addMember(Member $m): void { $this->members[] = $m; } diff --git a/src/Data/User.php b/src/Core/Data/User.php similarity index 93% rename from src/Data/User.php rename to src/Core/Data/User.php index 6cb55c2..004379d 100755 --- a/src/Data/User.php +++ b/src/Core/Data/User.php @@ -1,6 +1,6 @@ con = $con; } diff --git a/src/Gateway/AuthGateway.php b/src/Core/Gateway/AuthGateway.php similarity index 86% rename from src/Gateway/AuthGateway.php rename to src/Core/Gateway/AuthGateway.php index 3370d26..a748f39 100644 --- a/src/Gateway/AuthGateway.php +++ b/src/Core/Gateway/AuthGateway.php @@ -1,17 +1,17 @@ con = $con; } diff --git a/src/Gateway/TacticInfoGateway.php b/src/Core/Gateway/TacticInfoGateway.php similarity index 87% rename from src/Gateway/TacticInfoGateway.php rename to src/Core/Gateway/TacticInfoGateway.php index 56e9b42..2031d04 100644 --- a/src/Gateway/TacticInfoGateway.php +++ b/src/Core/Gateway/TacticInfoGateway.php @@ -1,18 +1,18 @@ con = $con; } @@ -38,7 +38,7 @@ class TacticInfoGateway { * @param integer $nb * @return array> */ - public function getLast(int $nb) : ?array { + public function getLast(int $nb): ?array { $res = $this->con->fetch( "SELECT * FROM Tactic ORDER BY creation_date DESC LIMIT :nb ", [":nb" => [$nb, PDO::PARAM_INT]] diff --git a/src/Gateway/TeamGateway.php b/src/Core/Gateway/TeamGateway.php similarity index 68% rename from src/Gateway/TeamGateway.php rename to src/Core/Gateway/TeamGateway.php index c98d803..942d2a4 100644 --- a/src/Gateway/TeamGateway.php +++ b/src/Core/Gateway/TeamGateway.php @@ -1,14 +1,14 @@ con = $con; } @@ -25,18 +25,22 @@ class TeamGateway { } - public function insertMember(int $idTeam, int $idMember, string $role) { + public function insertMember(int $idTeam, int $idMember, string $role): void { $this->con->exec( "INSERT INTO Member(idTeam, idMember, role) VALUES (:idTeam , :idMember, :role)", [ ":idTeam" => [$idTeam, PDO::PARAM_INT], ":idMember" => [$idMember, PDO::PARAM_INT], - ":role" => [$role, PDO::PARAM_STR] + ":role" => [$role, PDO::PARAM_STR], ] ); } + /** + * @param string $name + * @return array[] + */ public function listByName(string $name): array { return $this->con->fetch( "SELECT id,name,picture,mainColor,secondColor FROM Team WHERE name LIKE '%' || :name || '%'", @@ -48,28 +52,28 @@ class TeamGateway { /** * @param int $id - * @return array[] + * @return array */ - public function getTeamById(int $id): array { + public function getTeamById(int $id): ?array { return $this->con->fetch( "SELECT id,name,picture,mainColor,secondColor FROM Team WHERE id = :id", [ ":id" => [$id, PDO::PARAM_INT], ] - ); + )[0] ?? null; } /** * @param string $name - * @return array[] + * @return int|null */ - public function getIdTeamByName(string $name): array { + public function getIdTeamByName(string $name): ?int { return $this->con->fetch( "SELECT id FROM Team WHERE name = :name", [ - ":name" => [$name, PDO::PARAM_STR], + ":name" => [$name, PDO::PARAM_INT], ] - ); + )[0]['id'] ?? null; } /** @@ -78,23 +82,27 @@ class TeamGateway { */ public function getMembersById(int $id): array { return $this->con->fetch( - "SELECT a.id,m.role,a.email,a.username FROM Account a,Team t,Member m WHERE t.id = :id AND m.idTeam = t.id AND m.idMember = a.id", - [ - ":id" => [$id, PDO::PARAM_INT] + "SELECT a.id,m.role,a.email,a.username FROM Account a,Team t,Member m WHERE t.id = :id AND m.idTeam = t.id AND m.idMember = a.id", + [ + ":id" => [$id, PDO::PARAM_INT], ] ); } - public function getMemberIdByMail($mail) : array { + /** + * @param string $mail + * @return int|null + */ + public function getMemberIdByMail(string $mail): ?int { return $this->con->fetch( "SELECT id FROM Account WHERE email = :mail", [ - ":mail" => [$mail, PDO::PARAM_STR] + ":mail" => [$mail, PDO::PARAM_STR], ] - ); + )[0]['id'] ?? null; } - public function deleteMember(int $idTeam, int $idMember) { + public function deleteMember(int $idTeam, int $idMember): void { $this->con->exec( "DELETE FROM Member WHERE idTeam = :idTeam AND idMember = :idMember", [ diff --git a/src/Http/HttpCodes.php b/src/Core/Http/HttpCodes.php similarity index 90% rename from src/Http/HttpCodes.php rename to src/Core/Http/HttpCodes.php index c2b01df..d0a00c5 100644 --- a/src/Http/HttpCodes.php +++ b/src/Core/Http/HttpCodes.php @@ -1,6 +1,6 @@ > */ - public function getLast(int $nb) : ?array { + public function getLast(int $nb): ?array { return $this->tactics->getLast($nb); } diff --git a/src/Model/TeamModel.php b/src/Core/Model/TeamModel.php similarity index 72% rename from src/Model/TeamModel.php rename to src/Core/Model/TeamModel.php index c936d30..fc2afc8 100644 --- a/src/Model/TeamModel.php +++ b/src/Core/Model/TeamModel.php @@ -1,12 +1,12 @@ gateway->insert($name, $picture, $mainColor, $secondColor); - $result = $this->gateway->getIdTeamByName($name); - return intval($result[0]['id']); + return $this->gateway->getIdTeamByName($name); } - - public function addMember(string $mail, int $teamId, string $role) : int { - $result = $this->gateway->getMemberIdByMail($mail)[0]; - $memberId = intval($result['id']); - $this->gateway->insertMember($teamId, $memberId, $role); + public function addMember(string $mail, int $teamId, string $role): int { + $id = $this->gateway->getMemberIdByMail($mail); + $this->gateway->insertMember($teamId, $id, $role); return $teamId; } + /** + * @param string $name + * @return Team[] + */ public function listByName(string $name): array { $teams = []; $results = $this->gateway->listByName($name); @@ -43,7 +44,7 @@ class TeamModel { public function displayTeam(int $id): Team { $members = []; - $result = $this->gateway->getTeamById($id)[0]; + $result = $this->gateway->getTeamById($id); $resultMembers = $this->gateway->getMembersById($id); foreach ($resultMembers as $row) { var_dump($row['role']); @@ -57,9 +58,8 @@ class TeamModel { return new Team(intval($result['id']), $result['name'], $result['picture'], Color::from($result['mainColor']), Color::from($result['secondColor']), $members); } - public function deleteMember(string $mail, int $teamId) : int { - $result = $this->gateway->getMemberIdByMail($mail)[0]; - $memberId = intval($result['id']); + public function deleteMember(string $mail, int $teamId): int { + $memberId = $this->gateway->getMemberIdByMail($mail); $this->gateway->deleteMember($teamId, $memberId); return $teamId; } diff --git a/src/Core/Route/AbstractAction.php b/src/Core/Route/AbstractAction.php new file mode 100644 index 0000000..443d071 --- /dev/null +++ b/src/Core/Route/AbstractAction.php @@ -0,0 +1,36 @@ +action = $action; + $this->isAuthRequired = $isAuthRequired; + } + + public function isAuthRequired(): bool { + return $this->isAuthRequired; + } + + /** + * @param mixed[] $params + * @param S $session + * @return HttpResponse + */ + public abstract function run(array $params, $session): HttpResponse; +} \ No newline at end of file diff --git a/src/Controller/Control.php b/src/Core/Route/Control.php similarity index 90% rename from src/Controller/Control.php rename to src/Core/Route/Control.php index 2b428d9..f29557d 100644 --- a/src/Controller/Control.php +++ b/src/Core/Route/Control.php @@ -1,14 +1,14 @@ Date: Fri, 24 Nov 2023 11:10:08 +0100 Subject: [PATCH 36/38] simplify actions, move ViewHttpResponse into src/App --- public/api/index.php | 76 ++---------- public/index.php | 113 ++++-------------- src/Api/API.php | 71 +++++++++++ src/Api/ApiAction.php | 46 ------- .../{ => Controller}/APIAuthController.php | 2 +- .../{ => Controller}/APITacticController.php | 23 +--- src/App/App.php | 90 ++++++++++++++ src/App/AppAction.php | 55 --------- src/{Core/Route => App}/Control.php | 2 +- src/App/Controller/AuthController.php | 2 +- src/App/Controller/EditorController.php | 2 +- src/App/Controller/TeamController.php | 2 +- src/App/Controller/UserController.php | 2 +- src/App/Controller/VisualizerController.php | 2 +- src/{Core/Http => App}/ViewHttpResponse.php | 5 +- src/Core/Action.php | 56 +++++++++ src/Core/Route/AbstractAction.php | 36 ------ 17 files changed, 258 insertions(+), 327 deletions(-) create mode 100644 src/Api/API.php delete mode 100644 src/Api/ApiAction.php rename src/Api/{ => Controller}/APIAuthController.php (96%) rename src/Api/{ => Controller}/APITacticController.php (60%) create mode 100644 src/App/App.php delete mode 100644 src/App/AppAction.php rename src/{Core/Route => App}/Control.php (98%) rename src/{Core/Http => App}/ViewHttpResponse.php (95%) create mode 100644 src/Core/Action.php delete mode 100644 src/Core/Route/AbstractAction.php diff --git a/public/api/index.php b/public/api/index.php index eeac0c1..076dd11 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -5,20 +5,16 @@ require "../../vendor/autoload.php"; require "../../sql/database.php"; require "../utils.php"; -use IQBall\Api\ApiAction; +use IQBall\Api\API; +use IQBall\Api\Controller\APIAuthController; +use IQBall\Api\Controller\APITacticController; +use IQBall\Core\Action; use IQBall\Core\Connection; -use IQBall\Api\APIAuthController; -use IQBall\Api\APITacticController; use IQBall\Core\Data\Account; use IQBall\Core\Gateway\AccountGateway; use IQBall\Core\Gateway\TacticInfoGateway; -use IQBall\Core\Http\HttpResponse; -use IQBall\Core\Http\JsonHttpResponse; -use IQBall\Core\Http\ViewHttpResponse; use IQBall\Core\Model\AuthModel; use IQBall\Core\Model\TacticModel; -use IQBall\Core\Session\PhpSessionHandle; -use IQBall\Core\Validation\ValidationFail; function getTacticController(): APITacticController { return new APITacticController(new TacticModel(new TacticInfoGateway(new Connection(get_database())))); @@ -32,68 +28,10 @@ function getRoutes(): AltoRouter { $router = new AltoRouter(); $router->setBasePath(get_public_path() . "/api"); - $router->map("POST", "/tactic/[i:id]/edit/name", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); - $router->map("GET", "/tactic/[i:id]", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc))); - $router->map("POST", "/tactic/new", ApiAction::auth(fn(Account $acc) => getTacticController()->newTactic($acc))); - $router->map("POST", "/auth", ApiAction::noAuth(fn() => getAuthController()->authorize())); + $router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc))); + $router->map("POST", "/auth", Action::noAuth(fn() => getAuthController()->authorize())); return $router; } -/** - * @param mixed[] $match - * @return HttpResponse - * @throws Exception - */ -function handleMatch(array $match): HttpResponse { - if (!$match) { - return new JsonHttpResponse([ValidationFail::notFound("not found")]); - } - - $action = $match['target']; - if (!$action instanceof ApiAction) { - throw new Exception("routed action is not an AppAction object."); - } - - $auth = null; - - if ($action->isAuthRequired()) { - $auth = tryGetAuthorization(); - if ($auth == null) { - return new JsonHttpResponse([ValidationFail::unauthorized("Missing or invalid 'Authorization' header.")]); - } - } - - return $action->run($match['params'], $auth); -} - -function tryGetAuthorization(): ?Account { - $headers = getallheaders(); - - // If no authorization header is set, try fallback to php session. - if (!isset($headers['Authorization'])) { - $session = PhpSessionHandle::init(); - return $session->getAccount(); - } - - $token = $headers['Authorization']; - $gateway = new AccountGateway(new Connection(get_database())); - return $gateway->getAccountFromToken($token); -} - -function render(HttpResponse $response): void { - http_response_code($response->getCode()); - - foreach ($response->getHeaders() as $header => $value) { - header("$header: $value"); - } - - if ($response instanceof JsonHttpResponse) { - header('Content-type: application/json'); - echo $response->getJson(); - } elseif ($response instanceof ViewHttpResponse) { - throw new Exception("API returned a view http response."); - } -} - -render(handleMatch(getRoutes()->match())); +Api::render(API::handleMatch(getRoutes()->match())); diff --git a/public/index.php b/public/index.php index 07d58c1..705284d 100644 --- a/public/index.php +++ b/public/index.php @@ -7,32 +7,22 @@ require "../sql/database.php"; require "../src/utils.php"; require "../src/App/react-display.php"; -use IQBall\App\AppAction; +use IQBall\App\App; use IQBall\App\Controller\AuthController; use IQBall\App\Controller\EditorController; use IQBall\App\Controller\TeamController; use IQBall\App\Controller\UserController; use IQBall\App\Controller\VisualizerController; +use IQBall\Core\Action; use IQBall\Core\Connection; use IQBall\Core\Gateway\AccountGateway; use IQBall\Core\Gateway\TacticInfoGateway; use IQBall\Core\Gateway\TeamGateway; -use IQBall\Core\Http\HttpCodes; -use IQBall\Core\Http\HttpResponse; -use IQBall\Core\Http\JsonHttpResponse; -use IQBall\Core\Http\ViewHttpResponse; use IQBall\Core\Model\AuthModel; use IQBall\Core\Model\TacticModel; use IQBall\Core\Model\TeamModel; -use IQBall\Core\Session\MutableSessionHandle; use IQBall\Core\Session\PhpSessionHandle; use IQBall\Core\Session\SessionHandle; -use IQBall\Core\Validation\ValidationFail; -use Twig\Environment; -use Twig\Error\LoaderError; -use Twig\Error\RuntimeError; -use Twig\Error\SyntaxError; -use Twig\Loader\FilesystemLoader; function getConnection(): Connection { return new Connection(get_database()); @@ -65,96 +55,37 @@ function getRoutes(): AltoRouter { $ar->setBasePath($basePath); //authentication - $ar->map("GET", "/login", AppAction::noAuth(fn() => getAuthController()->displayLogin())); - $ar->map("GET", "/register", AppAction::noAuth(fn() => getAuthController()->displayRegister())); - $ar->map("POST", "/login", AppAction::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s))); - $ar->map("POST", "/register", AppAction::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s))); + $ar->map("GET", "/login", Action::noAuth(fn() => getAuthController()->displayLogin())); + $ar->map("GET", "/register", Action::noAuth(fn() => getAuthController()->displayRegister())); + $ar->map("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s))); + $ar->map("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s))); //user-related - $ar->map("GET", "/home", AppAction::auth(fn(SessionHandle $s) => getUserController()->home($s))); - $ar->map("GET", "/settings", AppAction::auth(fn(SessionHandle $s) => getUserController()->settings($s))); + $ar->map("GET", "/home", Action::auth(fn(SessionHandle $s) => getUserController()->home($s))); + $ar->map("GET", "/", Action::auth(fn(SessionHandle $s) => getUserController()->home($s))); + $ar->map("GET", "/settings", Action::auth(fn(SessionHandle $s) => getUserController()->settings($s))); //tactic-related - $ar->map("GET", "/tactic/[i:id]/view", AppAction::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s))); - $ar->map("GET", "/tactic/[i:id]/edit", AppAction::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s))); - $ar->map("GET", "/tactic/new", AppAction::auth(fn(SessionHandle $s) => getEditorController()->createNew($s))); + $ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s))); + $ar->map("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s))); + $ar->map("GET", "/tactic/new", Action::auth(fn(SessionHandle $s) => getEditorController()->createNew($s))); //team-related - $ar->map("GET", "/team/new", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); - $ar->map("POST", "/team/new", AppAction::auth(fn(SessionHandle $s) => getTeamController()->submitTeam($_POST, $s))); - $ar->map("GET", "/team/search", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayListTeamByName($s))); - $ar->map("POST", "/team/search", AppAction::auth(fn(SessionHandle $s) => getTeamController()->listTeamByName($_POST, $s))); - $ar->map("GET", "/team/[i:id]", AppAction::auth(fn(int $id, SessionHandle $s) => getTeamController()->displayTeam($id, $s))); - $ar->map("GET", "/team/members/add", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayAddMember($s))); - $ar->map("POST", "/team/members/add", AppAction::auth(fn(SessionHandle $s) => getTeamController()->addMember($_POST, $s))); - $ar->map("GET", "/team/members/remove", AppAction::auth(fn(SessionHandle $s) => getTeamController()->displayDeleteMember($s))); - $ar->map("POST", "/team/members/remove", AppAction::auth(fn(SessionHandle $s) => getTeamController()->deleteMember($_POST, $s))); + $ar->map("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); + $ar->map("POST", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->submitTeam($_POST, $s))); + $ar->map("GET", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->displayListTeamByName($s))); + $ar->map("POST", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->listTeamByName($_POST, $s))); + $ar->map("GET", "/team/[i:id]", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->displayTeam($id, $s))); + $ar->map("GET", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->displayAddMember($s))); + $ar->map("POST", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->addMember($_POST, $s))); + $ar->map("GET", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->displayDeleteMember($s))); + $ar->map("POST", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->deleteMember($_POST, $s))); return $ar; } -function render(HttpResponse $response): void { - http_response_code($response->getCode()); - - foreach ($response->getHeaders() as $header => $value) { - header("$header: $value"); - } - - if ($response instanceof ViewHttpResponse) { - renderView($response); - } elseif ($response instanceof JsonHttpResponse) { - header('Content-type: application/json'); - echo $response->getJson(); - } -} - -function renderView(ViewHttpResponse $response): void { - $file = $response->getFile(); - $args = $response->getArguments(); - - switch ($response->getViewKind()) { - case ViewHttpResponse::REACT_VIEW: - send_react_front($file, $args); - break; - case ViewHttpResponse::TWIG_VIEW: - try { - $loader = new FilesystemLoader('../src/Views/'); - $twig = new Environment($loader); - $twig->display($file, $args); - } catch (RuntimeError | SyntaxError | LoaderError $e) { - http_response_code(500); - echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); - throw $e; - } - break; - } -} - -function runAction(AppAction $action, array $params, MutableSessionHandle $session): HttpResponse { - global $basePath; - if ($action->isAuthRequired()) { - $account = $session->getAccount(); - if ($account == null) { - // put in the session the initial url the user wanted to get - $session->setInitialTarget($_SERVER['REQUEST_URI']); - return HttpResponse::redirect($basePath . "/login"); - } - } - - return $action->run($params, $session); -} - -function runMatch(array $match, MutableSessionHandle $session): HttpResponse { - if (!$match) { - return ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], - ], HttpCodes::NOT_FOUND); - } - - return runAction($match['target'], $match['params'], $session); -} //this is a global variable $basePath = get_public_path(); -render(runMatch(getRoutes()->match(), PhpSessionHandle::init())); +App::render(App::runMatch(getRoutes()->match(), PhpSessionHandle::init())); diff --git a/src/Api/API.php b/src/Api/API.php new file mode 100644 index 0000000..d266e79 --- /dev/null +++ b/src/Api/API.php @@ -0,0 +1,71 @@ +getCode()); + + foreach ($response->getHeaders() as $header => $value) { + header("$header: $value"); + } + + if ($response instanceof JsonHttpResponse) { + header('Content-type: application/json'); + echo $response->getJson(); + } else { + throw new Exception("API returned a non-json response."); + } + } + + /** + * @param mixed[] $match + * @return HttpResponse + * @throws Exception + */ + public static function handleMatch(array $match): HttpResponse { + if (!$match) { + return new JsonHttpResponse([ValidationFail::notFound("not found")]); + } + + $action = $match['target']; + if (!$action instanceof Action) { + throw new Exception("routed action is not an AppAction object."); + } + + $auth = null; + + if ($action->isAuthRequired()) { + $auth = self::tryGetAuthorization(); + if ($auth == null) { + return new JsonHttpResponse([ValidationFail::unauthorized("Missing or invalid 'Authorization' header.")]); + } + } + + return $action->run($match['params'], $auth); + } + + private static function tryGetAuthorization(): ?Account { + $headers = getallheaders(); + + // If no authorization header is set, try fallback to php session. + if (!isset($headers['Authorization'])) { + $session = PhpSessionHandle::init(); + return $session->getAccount(); + } + + $token = $headers['Authorization']; + $gateway = new AccountGateway(new Connection(get_database())); + return $gateway->getAccountFromToken($token); + } +} diff --git a/src/Api/ApiAction.php b/src/Api/ApiAction.php deleted file mode 100644 index 9cfd0aa..0000000 --- a/src/Api/ApiAction.php +++ /dev/null @@ -1,46 +0,0 @@ - - */ -class ApiAction extends AbstractAction { - - /** - * @param mixed[] $params - * @param ?Account $session - * @return HttpResponse - */ - public function run(array $params, $session): HttpResponse { - $params = array_values($params); - if ($this->isAuthRequired()) { - if ($session == null) { - throw new \Exception("action requires authorization."); - } - $params[] = $session; - } - - return call_user_func_array($this->action, $params); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return ApiAction an action that does not require to have an authorization. - */ - public static function noAuth(callable $action): ApiAction { - return new ApiAction($action, false); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return ApiAction an action that does require to have an authorization. - */ - public static function auth(callable $action): ApiAction { - return new ApiAction($action, true); - } -} \ No newline at end of file diff --git a/src/Api/APIAuthController.php b/src/Api/Controller/APIAuthController.php similarity index 96% rename from src/Api/APIAuthController.php rename to src/Api/Controller/APIAuthController.php index c4f5976..59ec1d0 100644 --- a/src/Api/APIAuthController.php +++ b/src/Api/Controller/APIAuthController.php @@ -1,6 +1,6 @@ [Validators::lenBetween(1, 50), Validators::nameWithSpaces()], - ], function (HttpRequest $request) use ($account) { - $tactic = $this->model->makeNew($request["name"], $account->getId()); - $id = $tactic->getId(); - return new JsonHttpResponse(["id" => $id]); - }, true); - } - - public function getTacticInfo(int $id, Account $account): HttpResponse { - $tactic_info = $this->model->get($id); - - if ($tactic_info == null) { - return new JsonHttpResponse("could not find tactic #$id", HttpCodes::NOT_FOUND); - } - - return new JsonHttpResponse($tactic_info); - } - } diff --git a/src/App/App.php b/src/App/App.php new file mode 100644 index 0000000..9126afd --- /dev/null +++ b/src/App/App.php @@ -0,0 +1,90 @@ +getCode()); + + foreach ($response->getHeaders() as $header => $value) { + header("$header: $value"); + } + + if ($response instanceof ViewHttpResponse) { + self::renderView($response); + } elseif ($response instanceof JsonHttpResponse) { + header('Content-type: application/json'); + echo $response->getJson(); + } + } + + private static function renderView(ViewHttpResponse $response): void { + $file = $response->getFile(); + $args = $response->getArguments(); + + switch ($response->getViewKind()) { + case ViewHttpResponse::REACT_VIEW: + send_react_front($file, $args); + break; + case ViewHttpResponse::TWIG_VIEW: + try { + $loader = new FilesystemLoader('../src/App/Views/'); + $twig = new Environment($loader); + $twig->display($file, $args); + } catch (RuntimeError | SyntaxError | LoaderError $e) { + http_response_code(500); + echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s"); + throw $e; + } + break; + } + } + + /** + * @param Action $action + * @param mixed[] $params + * @param MutableSessionHandle $session + * @return HttpResponse + */ + private static function runAction(Action $action, array $params, MutableSessionHandle $session): HttpResponse { + global $basePath; + if ($action->isAuthRequired()) { + $account = $session->getAccount(); + if ($account == null) { + // put in the session the initial url the user wanted to get + $session->setInitialTarget($_SERVER['REQUEST_URI']); + return HttpResponse::redirect($basePath . "/login"); + } + } + + return $action->run($params, $session); + } + + /** + * @param array|false $match + * @param MutableSessionHandle $session + * @return HttpResponse + */ + public static function runMatch($match, MutableSessionHandle $session): HttpResponse { + if (!$match) { + return ViewHttpResponse::twig("error.html.twig", [ + 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], + ], HttpCodes::NOT_FOUND); + } + + return self::runAction($match['target'], $match['params'], $session); + } + +} diff --git a/src/App/AppAction.php b/src/App/AppAction.php deleted file mode 100644 index 0e4dcdf..0000000 --- a/src/App/AppAction.php +++ /dev/null @@ -1,55 +0,0 @@ - - */ -class AppAction extends AbstractAction { - - /** - * @param mixed[] $params - * @param MutableSessionHandle $session - * @return HttpResponse - * @throws Exception

- * thrown if this action is required to be authenticated, but the given session does not contain a logged-in account. - *

- *

- * Caller is supposed to ensure that the user is logged-in before, if `$this->isAuthRequired()` is true before - * running this action. - *

- */ - public function run(array $params, $session): HttpResponse { - $params = array_values($params); - if ($this->isAuthRequired()) { - if ($session->getAccount() == null) { - throw new Exception("action requires authorization."); - } - } - - $params[] = $session; - return call_user_func_array($this->action, $params); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return AppAction an action that does not require to have an authorization. - */ - public static function noAuth(callable $action): AppAction { - return new AppAction($action, false); - } - - /** - * @param callable(mixed[]): HttpResponse $action - * @return AppAction an action that does require to have an authorization. - */ - public static function auth(callable $action): AppAction { - return new AppAction($action, true); - } -} diff --git a/src/Core/Route/Control.php b/src/App/Control.php similarity index 98% rename from src/Core/Route/Control.php rename to src/App/Control.php index f29557d..3dea9b1 100644 --- a/src/Core/Route/Control.php +++ b/src/App/Control.php @@ -2,11 +2,11 @@ namespace IQBall\Core\Route; +use IQBall\App\ViewHttpResponse; use IQBall\Core\Http\HttpCodes; use IQBall\Core\Http\HttpRequest; use IQBall\Core\Http\HttpResponse; use IQBall\Core\Http\JsonHttpResponse; -use IQBall\Core\Http\ViewHttpResponse; use IQBall\Core\Validation\ValidationFail; use IQBall\Core\Validation\Validator; diff --git a/src/App/Controller/AuthController.php b/src/App/Controller/AuthController.php index f74e45d..46ef838 100644 --- a/src/App/Controller/AuthController.php +++ b/src/App/Controller/AuthController.php @@ -4,7 +4,7 @@ namespace IQBall\App\Controller; use IQBall\Core\Http\HttpRequest; use IQBall\Core\Http\HttpResponse; -use IQBall\Core\Http\ViewHttpResponse; +use IQBall\App\ViewHttpResponse; use IQBall\Core\Model\AuthModel; use IQBall\Core\Session\MutableSessionHandle; use IQBall\Core\Validation\ValidationFail; diff --git a/src/App/Controller/EditorController.php b/src/App/Controller/EditorController.php index 0b47cba..b004e10 100644 --- a/src/App/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -5,7 +5,7 @@ namespace IQBall\App\Controller; use IQBall\Core\Data\TacticInfo; use IQBall\Core\Http\HttpCodes; use IQBall\Core\Http\HttpResponse; -use IQBall\Core\Http\ViewHttpResponse; +use IQBall\App\ViewHttpResponse; use IQBall\Core\Model\TacticModel; use IQBall\Core\Session\SessionHandle; use IQBall\Core\Validator\TacticValidator; diff --git a/src/App/Controller/TeamController.php b/src/App/Controller/TeamController.php index d8db9e9..c34bfa8 100644 --- a/src/App/Controller/TeamController.php +++ b/src/App/Controller/TeamController.php @@ -4,7 +4,7 @@ namespace IQBall\App\Controller; use IQBall\Core\Http\HttpRequest; use IQBall\Core\Http\HttpResponse; -use IQBall\Core\Http\ViewHttpResponse; +use IQBall\App\ViewHttpResponse; use IQBall\Core\Model\TeamModel; use IQBall\Core\Session\SessionHandle; use IQBall\Core\Validation\FieldValidationFail; diff --git a/src/App/Controller/UserController.php b/src/App/Controller/UserController.php index 5d45038..7fd1a8d 100644 --- a/src/App/Controller/UserController.php +++ b/src/App/Controller/UserController.php @@ -3,7 +3,7 @@ namespace IQBall\App\Controller; use IQBall\Core\Http\HttpResponse; -use IQBall\Core\Http\ViewHttpResponse; +use IQBall\App\ViewHttpResponse; use IQBall\Core\Model\TacticModel; use IQBall\Core\Session\SessionHandle; diff --git a/src/App/Controller/VisualizerController.php b/src/App/Controller/VisualizerController.php index 46946dc..22a4367 100644 --- a/src/App/Controller/VisualizerController.php +++ b/src/App/Controller/VisualizerController.php @@ -4,7 +4,7 @@ namespace IQBall\App\Controller; use IQBall\Core\Http\HttpCodes; use IQBall\Core\Http\HttpResponse; -use IQBall\Core\Http\ViewHttpResponse; +use IQBall\App\ViewHttpResponse; use IQBall\Core\Model\TacticModel; use IQBall\Core\Session\SessionHandle; use IQBall\Core\Validator\TacticValidator; diff --git a/src/Core/Http/ViewHttpResponse.php b/src/App/ViewHttpResponse.php similarity index 95% rename from src/Core/Http/ViewHttpResponse.php rename to src/App/ViewHttpResponse.php index 1f7c692..dfbd1da 100644 --- a/src/Core/Http/ViewHttpResponse.php +++ b/src/App/ViewHttpResponse.php @@ -1,6 +1,9 @@ action = $action; + $this->isAuthRequired = $isAuthRequired; + } + + public function isAuthRequired(): bool { + return $this->isAuthRequired; + } + + /** + * @param mixed[] $params + * @param S $session + * @return HttpResponse + */ + public function run(array $params, $session): HttpResponse { + $params = array_values($params); + $params[] = $session; + return call_user_func_array($this->action, $params); + } + + /** + * @param callable(mixed[], S): HttpResponse $action + * @return Action an action that does not require to have an authorization. + */ + public static function noAuth(callable $action): Action { + return new Action($action, false); + } + + /** + * @param callable(mixed[], S): HttpResponse $action + * @return Action an action that does require to have an authorization. + */ + public static function auth(callable $action): Action { + return new Action($action, true); + } +} diff --git a/src/Core/Route/AbstractAction.php b/src/Core/Route/AbstractAction.php deleted file mode 100644 index 443d071..0000000 --- a/src/Core/Route/AbstractAction.php +++ /dev/null @@ -1,36 +0,0 @@ -action = $action; - $this->isAuthRequired = $isAuthRequired; - } - - public function isAuthRequired(): bool { - return $this->isAuthRequired; - } - - /** - * @param mixed[] $params - * @param S $session - * @return HttpResponse - */ - public abstract function run(array $params, $session): HttpResponse; -} \ No newline at end of file From 5b85ddce7da0c2b78227102ce7b0d21aea4c6729 Mon Sep 17 00:00:00 2001 From: samuel Date: Sat, 25 Nov 2023 19:00:22 +0100 Subject: [PATCH 37/38] model --- Documentation/models.puml | 33 ++++++++++++++++++++------------- Documentation/team.puml | 5 +++-- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Documentation/models.puml b/Documentation/models.puml index 82037bc..3482ab1 100755 --- a/Documentation/models.puml +++ b/Documentation/models.puml @@ -1,14 +1,15 @@ @startuml class Account { - - email: String - - phoneNumber: String + - email: string + - phoneNumber: string - id: int - + setMailAddress(String) - + getMailAddress(): String - + getPhoneNumber(): String - + setPhoneNumber(String) + + __construct (email : string, ???) + + setMailAddress(string) + + getMailAddress(): string + + getPhoneNumber(): string + + setPhoneNumber(string) + getUser(): AccountUser + getId(): int } @@ -17,17 +18,18 @@ Account --> "- user" AccountUser Account --> "- teams *" Team interface User { - + getName(): String + + getName(): string + getProfilePicture(): Url + getAge(): int } class AccountUser { - - name: String + - name: string - profilePicture: Url - age: int - + setName(String) + + __construct(name : string, age : int, profilePicture : string) + + setName(string) + setProfilePicture(URI) + setAge(int) } @@ -36,6 +38,7 @@ AccountUser ..|> User class Member { - userId: int + + __construct(role : MemberRole) + getUserId(): int + getRole(): MemberRole } @@ -48,10 +51,11 @@ enum MemberRole { } class Team { - - name: String + - name: string - picture: Url - + getName(): String + + __construct(name : string, picture : string, mainColor : Color, secondColor : Color,members : array) + + getName(): string + getPicture(): Url + getMainColor(): Color + getSecondColor(): Color @@ -59,7 +63,7 @@ class Team { } Team --> "- mainColor" Color -Team --> "- secondaryColor" Color +Team --> "- secondColor" Color Team --> "- members *" Member class Color { @@ -70,6 +74,7 @@ class Color { class AuthController{ + + __construct(model : AuthModel) + displayRegister() : HttpResponse + displayBadFields(viewName : string, fails : array) : HttpResponse + confirmRegister(request : array) : HttpResponse @@ -80,6 +85,7 @@ AuthController --> "- model" AuthModel class AuthModel{ + + __construct(gateway : AuthGateway) + register(username : string, password : string, confirmPassword : string, email : string): array + getAccount(email : string):array + login(email : string, password : string) @@ -89,7 +95,8 @@ AuthModel --> "- gateway" AuthGateway class AuthGateway{ -con : Connection - + mailExists(email : string) : bool + + __construct (con : Connection) + + mailExist(email : string) : bool + insertAccount(username : string, hash : string, email : string) + getHash(email : string):string + getAccount (email : string): array diff --git a/Documentation/team.puml b/Documentation/team.puml index a291b1d..78b6659 100644 --- a/Documentation/team.puml +++ b/Documentation/team.puml @@ -1,10 +1,11 @@ @startuml class Team { - - name: String + - name: string - picture: Url - members: array - + getName(): String + + __construct(name : string, picture : string, mainColor : Colo, secondColor : Color) + + getName(): string + getPicture(): Url + getMainColor(): Color + getSecondColor(): Color From 89c3e72c8f4e3bb95ad4570da69c82a6640e8382 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Sat, 25 Nov 2023 20:35:00 +0100 Subject: [PATCH 38/38] add and edit documentation, clean code --- Documentation/how-to-dev.md | 17 +--- Documentation/http.puml | 4 + Documentation/models.puml | 93 +++++++-------------- Documentation/mvc/auth.puml | 28 +++++++ Documentation/{ => mvc}/team.puml | 8 +- Documentation/php.puml | 8 -- Documentation/validation.puml | 2 + public/index.php | 30 +++++-- sql/setup-tables.sql | 14 ++-- src/Api/Controller/APIAuthController.php | 9 +- src/Api/Controller/APITacticController.php | 6 ++ src/App/App.php | 53 ++++++------ src/App/Controller/AuthController.php | 28 +++---- src/App/Controller/EditorController.php | 21 +++-- src/App/Controller/TeamController.php | 64 ++++++++++---- src/App/Controller/UserController.php | 10 +-- src/App/Controller/VisualizerController.php | 8 +- src/App/Views/display_team.html.twig | 24 +++--- src/App/Views/insert_team.html.twig | 8 +- src/Core/Action.php | 2 + src/Core/Data/AccountUser.php | 52 ------------ src/Core/Data/MemberRole.php | 23 ++++- src/Core/Data/TacticInfo.php | 19 ++--- src/Core/Data/Team.php | 59 ++----------- src/Core/Data/TeamInfo.php | 49 +++++++++++ src/Core/Data/User.php | 26 ------ src/Core/Gateway/AccountGateway.php | 13 ++- src/Core/Gateway/AuthGateway.php | 47 ----------- src/Core/Gateway/MemberGateway.php | 69 +++++++++++++++ src/Core/Gateway/TacticInfoGateway.php | 16 ++++ src/Core/Gateway/TeamGateway.php | 90 +++++++------------- src/Core/Http/HttpCodes.php | 1 + src/Core/Http/HttpResponse.php | 9 ++ src/Core/Model/AuthModel.php | 10 ++- src/Core/Model/TacticModel.php | 13 ++- src/Core/Model/TeamModel.php | 87 +++++++++++-------- src/Core/Validation/ValidationFail.php | 8 ++ src/Core/Validation/Validators.php | 17 +++- src/Core/Validator/TacticValidator.php | 7 +- 39 files changed, 563 insertions(+), 489 deletions(-) create mode 100644 Documentation/mvc/auth.puml rename Documentation/{ => mvc}/team.puml (90%) delete mode 100644 Documentation/php.puml delete mode 100755 src/Core/Data/AccountUser.php create mode 100644 src/Core/Data/TeamInfo.php delete mode 100755 src/Core/Data/User.php delete mode 100644 src/Core/Gateway/AuthGateway.php create mode 100644 src/Core/Gateway/MemberGateway.php diff --git a/Documentation/how-to-dev.md b/Documentation/how-to-dev.md index 0af8da5..39435e6 100644 --- a/Documentation/how-to-dev.md +++ b/Documentation/how-to-dev.md @@ -28,7 +28,7 @@ If we take a look at the request, we'll see that the url does not targets `local `localhost:5173` is the react development server, it is able to serve our react front view files. Let's run the react development server. -It is a simple as running `npm start` in a new terminal (be sure to run it in the repository's directory). +It is as simple as running `npm start` in a new terminal (be sure to run it in the repository's directory). ![](assets/npm-start.png) You should see something like this, it says that the server was opened on port `5173`, thats our react development server ! @@ -40,7 +40,7 @@ Caution: **NEVER** directly connect on the `localhost:5173` node development ser # How it works I'm glad you are interested in how that stuff works, it's a bit tricky, lets go. -If you look at our `index.php` (located in `/public` folder), you'll see that it is our gateway, it uses an `AltoRouter` that dispatches the request's process to a controller. +If you look at our `index.php` (located in `/public` folder), you'll see that it define our routes, it uses an `AltoRouter` then delegates the request's action processing to a controller. We can see that there are two registered routes, the `GET:/` (that then calls `SampleFormController#displayForm()`) and `POST:/result` (that calls `SampleFormController#displayResults()`). Implementation of those two methods are very simple: there is no verification to make nor model to control, thus they directly sends the view back to the client. @@ -115,7 +115,7 @@ function _asset(string $assetURI): string { The simplest profile, simply redirect all assets to the development server ### Production profile -Before the CD deploys the generated files to the server, +Before the CD workflow step deploys the generated files to the server, it generates a `/views-mappings.php` file that will map the react views file names to their compiled javascript files : ```php @@ -137,13 +137,4 @@ function _asset(string $assetURI): string { // fallback to the uri itself. return $basePath . "/" . (ASSETS[$assetURI] ?? $assetURI); } -``` - -## React views conventions. -Conventions regarding our react views __must be respected in order to be renderable__. - -### The `render(any)` function -Any React view component needs to be default exported in order to be imported and used from PHP. Those components will receive as props the arguments that the PHP server has transmitted. -The `arguments` parameter is used to pass data to the react component. - -If you take a look at the `front/views/SampleForm.tsx` view, here's the definition of its render function : +``` \ No newline at end of file diff --git a/Documentation/http.puml b/Documentation/http.puml index 9fbe606..2f8e22b 100644 --- a/Documentation/http.puml +++ b/Documentation/http.puml @@ -44,4 +44,8 @@ class ViewHttpResponse extends HttpResponse { + react(file: string, arguments: array, code: int = HttpCodes::OK): ViewHttpResponse } +note right of ViewHttpResponse + Into src/App +end note + @enduml \ No newline at end of file diff --git a/Documentation/models.puml b/Documentation/models.puml index 3482ab1..c0629d4 100755 --- a/Documentation/models.puml +++ b/Documentation/models.puml @@ -1,45 +1,36 @@ @startuml -class Account { - - email: string - - phoneNumber: string +class TacticInfo { - id: int - - + __construct (email : string, ???) - + setMailAddress(string) - + getMailAddress(): string - + getPhoneNumber(): string - + setPhoneNumber(string) - + getUser(): AccountUser + - name: string + - creationDate: string + - ownerId: string + + getId(): int -} - -Account --> "- user" AccountUser -Account --> "- teams *" Team - -interface User { + + getOwnerId(): int + + getCreationTimestamp(): int + getName(): string - + getProfilePicture(): Url - + getAge(): int } -class AccountUser { +class Account { + - email: string + - token: string - name: string - - profilePicture: Url - - age: int + - id: int - + __construct(name : string, age : int, profilePicture : string) - + setName(string) - + setProfilePicture(URI) - + setAge(int) + + getMailAddress(): string + + getToken(): string + + getName(): string + + getId(): int } -AccountUser ..|> User class Member { - userId: int + - teamId: int + __construct(role : MemberRole) + getUserId(): int + + getTeamId(): int + getRole(): MemberRole } @@ -50,20 +41,27 @@ enum MemberRole { COACH } -class Team { + +class TeamInfo { + - creationDate: int - name: string - - picture: Url + - picture: string - + __construct(name : string, picture : string, mainColor : Color, secondColor : Color,members : array) + getName(): string - + getPicture(): Url + + getPicture(): string + getMainColor(): Color + getSecondColor(): Color - + listMembers(): array } -Team --> "- mainColor" Color -Team --> "- secondColor" Color +TeamInfo --> "- mainColor" Color +TeamInfo --> "- secondaryColor" Color + +class Team { + getInfo(): TeamInfo + listMembers(): Member[] +} + +Team --> "- info" TeamInfo Team --> "- members *" Member class Color { @@ -72,33 +70,4 @@ class Color { + getValue(): int } -class AuthController{ - - + __construct(model : AuthModel) - + displayRegister() : HttpResponse - + displayBadFields(viewName : string, fails : array) : HttpResponse - + confirmRegister(request : array) : HttpResponse - + displayLogin() : HttpResponse - + confirmLogin() : HttpResponse -} -AuthController --> "- model" AuthModel - -class AuthModel{ - - + __construct(gateway : AuthGateway) - + register(username : string, password : string, confirmPassword : string, email : string): array - + getAccount(email : string):array - + login(email : string, password : string) -} -AuthModel --> "- gateway" AuthGateway - -class AuthGateway{ - -con : Connection - - + __construct (con : Connection) - + mailExist(email : string) : bool - + insertAccount(username : string, hash : string, email : string) - + getHash(email : string):string - + getAccount (email : string): array -} @enduml \ No newline at end of file diff --git a/Documentation/mvc/auth.puml b/Documentation/mvc/auth.puml new file mode 100644 index 0000000..d416bc3 --- /dev/null +++ b/Documentation/mvc/auth.puml @@ -0,0 +1,28 @@ +@startuml + +class AuthController { + + displayRegister() : HttpResponse + + displayBadFields(viewName : string, fails : array) : HttpResponse + + confirmRegister(request : array) : HttpResponse + + displayLogin() : HttpResponse + + confirmLogin() : HttpResponse +} +AuthController --> "- model" AuthModel + +class AuthModel { + + register(username : string, password : string, confirmPassword : string, email : string): array + + getAccount(email : string):array + + login(email : string, password : string) +} +AuthModel --> "- gateway" AuthGateway + +class AuthGateway { + -con : Connection + + + mailExists(email : string) : bool + + insertAccount(username : string, hash : string, email : string) + + getUserHash(email : string):string + + getAccount (email : string): array +} + +@enduml \ No newline at end of file diff --git a/Documentation/team.puml b/Documentation/mvc/team.puml similarity index 90% rename from Documentation/team.puml rename to Documentation/mvc/team.puml index 78b6659..ad5e201 100644 --- a/Documentation/team.puml +++ b/Documentation/mvc/team.puml @@ -58,12 +58,6 @@ class TeamController{ TeamController *--"- model" TeamModel -class Connexion{ - - pdo : PDO - -- - + __constructor(pdo : PDO) - + exec(query : string, args : array) - + fetch(query string, args array): array -} +class Connexion { } @enduml \ No newline at end of file diff --git a/Documentation/php.puml b/Documentation/php.puml deleted file mode 100644 index 8641900..0000000 --- a/Documentation/php.puml +++ /dev/null @@ -1,8 +0,0 @@ -@startuml - -class FrontController { - - router : AltoRouter - -} - -@enduml \ No newline at end of file diff --git a/Documentation/validation.puml b/Documentation/validation.puml index dd0cafe..f509cf4 100644 --- a/Documentation/validation.puml +++ b/Documentation/validation.puml @@ -50,9 +50,11 @@ class Validation { } class Validators { + --- + nonEmpty(): Validator + shorterThan(limit: int): Validator + userString(maxLen: int): Validator + ... } diff --git a/public/index.php b/public/index.php index 705284d..807ec88 100644 --- a/public/index.php +++ b/public/index.php @@ -13,16 +13,22 @@ use IQBall\App\Controller\EditorController; use IQBall\App\Controller\TeamController; use IQBall\App\Controller\UserController; use IQBall\App\Controller\VisualizerController; +use IQBall\App\ViewHttpResponse; use IQBall\Core\Action; use IQBall\Core\Connection; use IQBall\Core\Gateway\AccountGateway; +use IQBall\Core\Gateway\MemberGateway; use IQBall\Core\Gateway\TacticInfoGateway; use IQBall\Core\Gateway\TeamGateway; +use IQBall\Core\Http\HttpCodes; +use IQBall\Core\Http\HttpResponse; use IQBall\Core\Model\AuthModel; use IQBall\Core\Model\TacticModel; use IQBall\Core\Model\TeamModel; +use IQBall\Core\Session\MutableSessionHandle; use IQBall\Core\Session\PhpSessionHandle; use IQBall\Core\Session\SessionHandle; +use IQBall\Core\Validation\ValidationFail; function getConnection(): Connection { return new Connection(get_database()); @@ -41,7 +47,8 @@ function getEditorController(): EditorController { } function getTeamController(): TeamController { - return new TeamController(new TeamModel(new TeamGateway(getConnection()))); + $con = getConnection(); + return new TeamController(new TeamModel(new TeamGateway($con), new MemberGateway($con), new AccountGateway($con))); } function getAuthController(): AuthController { @@ -57,8 +64,8 @@ function getRoutes(): AltoRouter { //authentication $ar->map("GET", "/login", Action::noAuth(fn() => getAuthController()->displayLogin())); $ar->map("GET", "/register", Action::noAuth(fn() => getAuthController()->displayRegister())); - $ar->map("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s))); - $ar->map("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s))); + $ar->map("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->login($_POST, $s))); + $ar->map("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->register($_POST, $s))); //user-related $ar->map("GET", "/home", Action::auth(fn(SessionHandle $s) => getUserController()->home($s))); @@ -66,8 +73,8 @@ function getRoutes(): AltoRouter { $ar->map("GET", "/settings", Action::auth(fn(SessionHandle $s) => getUserController()->settings($s))); //tactic-related - $ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s))); - $ar->map("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s))); + $ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->openVisualizer($id, $s))); + $ar->map("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->openEditor($id, $s))); $ar->map("GET", "/tactic/new", Action::auth(fn(SessionHandle $s) => getEditorController()->createNew($s))); //team-related @@ -84,8 +91,19 @@ function getRoutes(): AltoRouter { return $ar; } +function runMatch($match, MutableSessionHandle $session): HttpResponse { + global $basePath; + if (!$match) { + return ViewHttpResponse::twig("error.html.twig", [ + 'failures' => [ValidationFail::notFound("Could not find page {$_SERVER['REQUEST_URI']}.")], + ], HttpCodes::NOT_FOUND); + } + + return App::runAction($basePath . '/login', $match['target'], $match['params'], $session); +} + //this is a global variable $basePath = get_public_path(); -App::render(App::runMatch(getRoutes()->match(), PhpSessionHandle::init())); +App::render(runMatch(getRoutes()->match(), PhpSessionHandle::init()), "../src/App/Views/"); diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index b13712f..324fb39 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -30,15 +30,15 @@ CREATE TABLE Team id integer PRIMARY KEY AUTOINCREMENT, name varchar, picture varchar, - mainColor varchar, - secondColor varchar + main_color varchar, + second_color varchar ); CREATE TABLE Member( - idTeam integer, - idMember integer, - role char(1) CHECK (role IN ('C', 'P')), - FOREIGN KEY (idTeam) REFERENCES Team (id), - FOREIGN KEY (idMember) REFERENCES User (id) + id_team integer, + id_user integer, + role char(1) CHECK (role IN ('Coach', 'Player')), + FOREIGN KEY (id_team) REFERENCES Team (id), + FOREIGN KEY (id_user) REFERENCES User (id) ); diff --git a/src/Api/Controller/APIAuthController.php b/src/Api/Controller/APIAuthController.php index 59ec1d0..332f260 100644 --- a/src/Api/Controller/APIAuthController.php +++ b/src/Api/Controller/APIAuthController.php @@ -2,6 +2,7 @@ namespace IQBall\Api\Controller; +use IQBall\Core\Http\HttpCodes; use IQBall\Core\Route\Control; use IQBall\Core\Http\HttpRequest; use IQBall\Core\Http\HttpResponse; @@ -20,16 +21,20 @@ class APIAuthController { } + /** + * From given email address and password, authenticate the user and respond with its authorization token. + * @return HttpResponse + */ public function authorize(): HttpResponse { return Control::runChecked([ - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], + "email" => [Validators::email(), Validators::lenBetween(5, 256)], "password" => [Validators::lenBetween(6, 256)], ], function (HttpRequest $req) { $failures = []; $account = $this->model->login($req["email"], $req["password"], $failures); if (!empty($failures)) { - return new JsonHttpResponse($failures); + return new JsonHttpResponse($failures, HttpCodes::UNAUTHORIZED); } return new JsonHttpResponse(["authorization" => $account->getToken()]); diff --git a/src/Api/Controller/APITacticController.php b/src/Api/Controller/APITacticController.php index cba1fda..63e4811 100644 --- a/src/Api/Controller/APITacticController.php +++ b/src/Api/Controller/APITacticController.php @@ -24,6 +24,12 @@ class APITacticController { $this->model = $model; } + /** + * update name of tactic, specified by tactic identifier, given in url. + * @param int $tactic_id + * @param Account $account + * @return HttpResponse + */ public function updateName(int $tactic_id, Account $account): HttpResponse { return Control::runChecked([ "name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()], diff --git a/src/App/App.php b/src/App/App.php index 9126afd..7fa2595 100644 --- a/src/App/App.php +++ b/src/App/App.php @@ -3,11 +3,9 @@ namespace IQBall\App; use IQBall\Core\Action; -use IQBall\Core\Http\HttpCodes; use IQBall\Core\Http\HttpResponse; use IQBall\Core\Http\JsonHttpResponse; use IQBall\Core\Session\MutableSessionHandle; -use IQBall\Core\Validation\ValidationFail; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; @@ -15,7 +13,16 @@ use Twig\Error\SyntaxError; use Twig\Loader\FilesystemLoader; class App { - public static function render(HttpResponse $response): void { + /** + * renders (prints out) given HttpResponse to the client + * @param HttpResponse $response + * @param string $twigViewsFolder + * @return void + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError + */ + public static function render(HttpResponse $response, string $twigViewsFolder): void { http_response_code($response->getCode()); foreach ($response->getHeaders() as $header => $value) { @@ -23,14 +30,23 @@ class App { } if ($response instanceof ViewHttpResponse) { - self::renderView($response); + self::renderView($response, $twigViewsFolder); } elseif ($response instanceof JsonHttpResponse) { header('Content-type: application/json'); echo $response->getJson(); } } - private static function renderView(ViewHttpResponse $response): void { + /** + * renders (prints out) given ViewHttpResponse to the client + * @param ViewHttpResponse $response + * @param string $twigViewsFolder + * @return void + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError + */ + private static function renderView(ViewHttpResponse $response, string $twigViewsFolder): void { $file = $response->getFile(); $args = $response->getArguments(); @@ -40,8 +56,8 @@ class App { break; case ViewHttpResponse::TWIG_VIEW: try { - $loader = new FilesystemLoader('../src/App/Views/'); - $twig = new Environment($loader); + $fl = new FilesystemLoader($twigViewsFolder); + $twig = new Environment($fl); $twig->display($file, $args); } catch (RuntimeError | SyntaxError | LoaderError $e) { http_response_code(500); @@ -53,38 +69,25 @@ class App { } /** + * run a user action, and return the generated response + * @param string $authRoute the route towards an authentication page to response with a redirection + * if the run action requires auth but session does not contain a logged-in account. * @param Action $action * @param mixed[] $params * @param MutableSessionHandle $session * @return HttpResponse */ - private static function runAction(Action $action, array $params, MutableSessionHandle $session): HttpResponse { - global $basePath; + public static function runAction(string $authRoute, Action $action, array $params, MutableSessionHandle $session): HttpResponse { if ($action->isAuthRequired()) { $account = $session->getAccount(); if ($account == null) { // put in the session the initial url the user wanted to get $session->setInitialTarget($_SERVER['REQUEST_URI']); - return HttpResponse::redirect($basePath . "/login"); + return HttpResponse::redirect($authRoute); } } return $action->run($params, $session); } - /** - * @param array|false $match - * @param MutableSessionHandle $session - * @return HttpResponse - */ - public static function runMatch($match, MutableSessionHandle $session): HttpResponse { - if (!$match) { - return ViewHttpResponse::twig("error.html.twig", [ - 'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")], - ], HttpCodes::NOT_FOUND); - } - - return self::runAction($match['target'], $match['params'], $session); - } - } diff --git a/src/App/Controller/AuthController.php b/src/App/Controller/AuthController.php index 46ef838..9e790e9 100644 --- a/src/App/Controller/AuthController.php +++ b/src/App/Controller/AuthController.php @@ -25,33 +25,25 @@ class AuthController { } /** - * @param string $viewName - * @param ValidationFail[] $fails - * @return HttpResponse - */ - private function displayBadFields(string $viewName, array $fails): HttpResponse { - return ViewHttpResponse::twig($viewName, ['fails' => $fails]); - } - - /** + * registers given account * @param mixed[] $request * @param MutableSessionHandle $session * @return HttpResponse */ - public function confirmRegister(array $request, MutableSessionHandle $session): HttpResponse { + public function register(array $request, MutableSessionHandle $session): HttpResponse { $fails = []; $request = HttpRequest::from($request, $fails, [ "username" => [Validators::name(), Validators::lenBetween(2, 32)], "password" => [Validators::lenBetween(6, 256)], "confirmpassword" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/", "invalide"),Validators::lenBetween(5, 256)], + "email" => [Validators::email(), Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { - return $this->displayBadFields("display_register.html.twig", $fails); + return ViewHttpResponse::twig("display_register.html.twig", ['fails' => $fails]); } $account = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email'], $fails); if (!empty($fails)) { - return $this->displayBadFields("display_register.html.twig", $fails); + return ViewHttpResponse::twig("display_register.html.twig", ['fails' => $fails]); } $session->setAccount($account); @@ -66,22 +58,24 @@ class AuthController { } /** + * logins given account credentials * @param mixed[] $request + * @param MutableSessionHandle $session * @return HttpResponse */ - public function confirmLogin(array $request, MutableSessionHandle $session): HttpResponse { + public function login(array $request, MutableSessionHandle $session): HttpResponse { $fails = []; $request = HttpRequest::from($request, $fails, [ "password" => [Validators::lenBetween(6, 256)], - "email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/", "invalide"),Validators::lenBetween(5, 256)], + "email" => [Validators::email(), Validators::lenBetween(5, 256)], ]); if (!empty($fails)) { - return $this->displayBadFields("display_login.html.twig", $fails); + return ViewHttpResponse::twig("display_login.html.twig", ['fails' => $fails]); } $account = $this->model->login($request['email'], $request['password'], $fails); if (!empty($fails)) { - return $this->displayBadFields("display_login.html.twig", $fails); + return ViewHttpResponse::twig("display_login.html.twig", ['fails' => $fails]); } $session->setAccount($account); diff --git a/src/App/Controller/EditorController.php b/src/App/Controller/EditorController.php index b004e10..6724ced 100644 --- a/src/App/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -17,22 +17,31 @@ class EditorController { $this->model = $model; } - private function openEditor(TacticInfo $tactic): HttpResponse { + /** + * @param TacticInfo $tactic + * @return ViewHttpResponse the editor view for given tactic + */ + private function openEditorFor(TacticInfo $tactic): ViewHttpResponse { return ViewHttpResponse::react("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]); } - public function createNew(SessionHandle $session): HttpResponse { + /** + * creates a new empty tactic, with default name + * @param SessionHandle $session + * @return ViewHttpResponse the editor view + */ + public function createNew(SessionHandle $session): ViewHttpResponse { $tactic = $this->model->makeNewDefault($session->getAccount()->getId()); - return $this->openEditor($tactic); + return $this->openEditorFor($tactic); } /** * returns an editor view for a given tactic * @param int $id the targeted tactic identifier * @param SessionHandle $session - * @return HttpResponse + * @return ViewHttpResponse */ - public function edit(int $id, SessionHandle $session): HttpResponse { + public function openEditor(int $id, SessionHandle $session): ViewHttpResponse { $tactic = $this->model->get($id); $failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId()); @@ -41,7 +50,7 @@ class EditorController { return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); } - return $this->openEditor($tactic); + return $this->openEditorFor($tactic); } diff --git a/src/App/Controller/TeamController.php b/src/App/Controller/TeamController.php index c34bfa8..deaf61b 100644 --- a/src/App/Controller/TeamController.php +++ b/src/App/Controller/TeamController.php @@ -20,20 +20,34 @@ class TeamController { $this->model = $model; } - public function displayCreateTeam(SessionHandle $session): HttpResponse { + /** + * @param SessionHandle $session + * @return ViewHttpResponse the team creation panel + */ + public function displayCreateTeam(SessionHandle $session): ViewHttpResponse { return ViewHttpResponse::twig("insert_team.html.twig", []); } - public function displayAddMember(SessionHandle $session): HttpResponse { + /** + * @param SessionHandle $session + * @return ViewHttpResponse the team panel to add a member + */ + public function displayAddMember(SessionHandle $session): ViewHttpResponse { return ViewHttpResponse::twig("add_member.html.twig", []); } - public function displayDeleteMember(SessionHandle $session): HttpResponse { + + /** + * @param SessionHandle $session + * @return ViewHttpResponse the team panel to delete a member + */ + public function displayDeleteMember(SessionHandle $session): ViewHttpResponse { return ViewHttpResponse::twig("delete_member.html.twig", []); } /** + * create a new team from given request name, mainColor, secondColor and picture url * @param array $request * @param SessionHandle $session * @return HttpResponse @@ -42,8 +56,8 @@ class TeamController { $failures = []; $request = HttpRequest::from($request, $failures, [ "name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()], - "mainColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')], - "secondColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')], + "main_color" => [Validators::hexColor()], + "second_color" => [Validators::hexColor()], "picture" => [Validators::isURL()], ]); if (!empty($failures)) { @@ -55,15 +69,22 @@ class TeamController { } return ViewHttpResponse::twig('insert_team.html.twig', ['bad_fields' => $badFields]); } - return $this->displayTeam($this->model->createTeam($request['name'], $request['picture'], $request['mainColor'], $request['secondColor']), $session); + $teamId = $this->model->createTeam($request['name'], $request['picture'], $request['main_color'], $request['second_color']); + return $this->displayTeam($teamId, $session); } - public function displayListTeamByName(SessionHandle $session): HttpResponse { + /** + * @param SessionHandle $session + * @return ViewHttpResponse the panel to search a team by its name + */ + public function displayListTeamByName(SessionHandle $session): ViewHttpResponse { return ViewHttpResponse::twig("list_team_by_name.html.twig", []); } /** - * @param array $request + * returns a view that contains all the teams description whose name matches the given name needle. + * @param array $request + * @param SessionHandle $session * @return HttpResponse */ public function listTeamByName(array $request, SessionHandle $session): HttpResponse { @@ -77,21 +98,27 @@ class TeamController { return ViewHttpResponse::twig('list_team_by_name.html.twig', ['bad_field' => $badField]); } - $results = $this->model->listByName($request['name']); + $teams = $this->model->listByName($request['name']); - if (empty($results)) { + if (empty($teams)) { return ViewHttpResponse::twig('display_teams.html.twig', []); } - return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $results]); + return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $teams]); } - public function displayTeam(int $id, SessionHandle $session): HttpResponse { - $result = $this->model->displayTeam($id); + /** + * @param int $id + * @param SessionHandle $session + * @return ViewHttpResponse a view that displays given team information + */ + public function displayTeam(int $id, SessionHandle $session): ViewHttpResponse { + $result = $this->model->getTeam($id); return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]); } /** + * add a member to a team * @param array $request * @param SessionHandle $session * @return HttpResponse @@ -101,13 +128,16 @@ class TeamController { $request = HttpRequest::from($request, $errors, [ "team" => [Validators::isInteger()], - "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], + "email" => [Validators::email(), Validators::lenBetween(5, 256)], ]); - return $this->displayTeam($this->model->addMember($request['mail'], intval($request['team']), $request['role']), $session); + $teamId = intval($request['team']); + $this->model->addMember($request['email'], $teamId, $request['role']); + return $this->displayTeam($teamId, $session); } /** + * remove a member from a team * @param array $request * @param SessionHandle $session * @return HttpResponse @@ -117,9 +147,9 @@ class TeamController { $request = HttpRequest::from($request, $errors, [ "team" => [Validators::isInteger()], - "mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)], + "email" => [Validators::email(), Validators::lenBetween(5, 256)], ]); - return $this->displayTeam($this->model->deleteMember($request['mail'], intval($request['team'])), $session); + return $this->displayTeam($this->model->deleteMember($request['email'], intval($request['team'])), $session); } } diff --git a/src/App/Controller/UserController.php b/src/App/Controller/UserController.php index 7fd1a8d..b7cef0b 100644 --- a/src/App/Controller/UserController.php +++ b/src/App/Controller/UserController.php @@ -19,18 +19,18 @@ class UserController { /** * @param SessionHandle $session - * @return HttpResponse the home page + * @return ViewHttpResponse the home page view */ - public function home(SessionHandle $session): HttpResponse { - //TODO use session's account to get the last 5 tactics if the logged-in account + public function home(SessionHandle $session): ViewHttpResponse { + //TODO use session's account to get the last 5 tactics of the logged-in account $listTactic = $this->tactics->getLast(5); return ViewHttpResponse::twig("home.twig", ["recentTactic" => $listTactic]); } /** - * @return HttpResponse account settings page + * @return ViewHttpResponse account settings page */ - public function settings(SessionHandle $session): HttpResponse { + public function settings(SessionHandle $session): ViewHttpResponse { return ViewHttpResponse::twig("account_settings.twig", []); } diff --git a/src/App/Controller/VisualizerController.php b/src/App/Controller/VisualizerController.php index 22a4367..48e1168 100644 --- a/src/App/Controller/VisualizerController.php +++ b/src/App/Controller/VisualizerController.php @@ -19,7 +19,13 @@ class VisualizerController { $this->tacticModel = $tacticModel; } - public function visualize(int $id, SessionHandle $session): HttpResponse { + /** + * opens a visualisation page for the tactic specified by its identifier in the url. + * @param int $id + * @param SessionHandle $session + * @return HttpResponse + */ + public function openVisualizer(int $id, SessionHandle $session): HttpResponse { $tactic = $this->tacticModel->get($id); $failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId()); diff --git a/src/App/Views/display_team.html.twig b/src/App/Views/display_team.html.twig index 474397e..6f0aec1 100644 --- a/src/App/Views/display_team.html.twig +++ b/src/App/Views/display_team.html.twig @@ -20,17 +20,13 @@ height:50px; } - #mainColor{ - background-color: {{ team.mainColor.getValue() }}; - {% if team.mainColor.getValue() == "#ffffff" %} - border-color: #666666; - {% endif %} + #main_color { + border: solid; + background-color: {{ team.getInfo().getMainColor().getValue() }}; } - #secondColor{ - background-color: {{ team.secondColor.getValue() }}; - {% if team.secondColor.getValue() == "#ffffff" %} - border-color: #666666; - {% endif %} + #second_color{ + background-color: {{ team.getInfo().getSecondColor().getValue() }}; + border: solid; } .container{ @@ -66,12 +62,12 @@
-

{{ team.name }}

- +

{{ team.getInfo().getName() }}

+
-

Couleur principale :

-

Couleur secondaire :

+

Couleur principale :

+

Couleur secondaire :

{% for m in team.listMembers() %} diff --git a/src/App/Views/insert_team.html.twig b/src/App/Views/insert_team.html.twig index 0cc85dd..0eafb55 100644 --- a/src/App/Views/insert_team.html.twig +++ b/src/App/Views/insert_team.html.twig @@ -70,10 +70,10 @@ - - - - + + + +
diff --git a/src/Core/Action.php b/src/Core/Action.php index c93958a..35721c1 100644 --- a/src/Core/Action.php +++ b/src/Core/Action.php @@ -5,6 +5,7 @@ namespace IQBall\Core; use IQBall\Core\Http\HttpResponse; /** + * Represent an action. * @template S session */ class Action { @@ -28,6 +29,7 @@ class Action { } /** + * Runs an action * @param mixed[] $params * @param S $session * @return HttpResponse diff --git a/src/Core/Data/AccountUser.php b/src/Core/Data/AccountUser.php deleted file mode 100755 index 2f8f0da..0000000 --- a/src/Core/Data/AccountUser.php +++ /dev/null @@ -1,52 +0,0 @@ -name = $name; - $this->profilePicture = $profilePicture; - $this->age = $age; - } - - - public function getName(): string { - return $this->name; - } - - public function getProfilePicture(): Url { - return $this->profilePicture; - } - - public function getAge(): int { - return $this->age; - } - - public function setName(string $name): void { - $this->name = $name; - } - - public function setProfilePicture(Url $profilePicture): void { - $this->profilePicture = $profilePicture; - } - - public function setAge(int $age): void { - $this->age = $age; - } - - -} diff --git a/src/Core/Data/MemberRole.php b/src/Core/Data/MemberRole.php index 7b63064..41b6b71 100755 --- a/src/Core/Data/MemberRole.php +++ b/src/Core/Data/MemberRole.php @@ -7,7 +7,7 @@ use InvalidArgumentException; /** * Enumeration class workaround * As there is no enumerations in php 7.4, this class - * encapsulates an integer value and use it as an enumeration discriminant + * encapsulates an integer value and use it as a variant discriminant */ final class MemberRole { private const ROLE_PLAYER = 0; @@ -32,6 +32,27 @@ final class MemberRole { return new MemberRole(MemberRole::ROLE_COACH); } + public function name(): string { + switch ($this->value) { + case self::ROLE_COACH: + return "Coach"; + case self::ROLE_PLAYER: + return "Player"; + } + die("unreachable"); + } + + public static function fromName(string $name): ?MemberRole { + switch ($name) { + case "Coach": + return MemberRole::coach(); + case "Player": + return MemberRole::player(); + default: + return null; + } + } + private function isValid(int $val): bool { return ($val <= self::MAX and $val >= self::MIN); } diff --git a/src/Core/Data/TacticInfo.php b/src/Core/Data/TacticInfo.php index 6f31680..04f592a 100644 --- a/src/Core/Data/TacticInfo.php +++ b/src/Core/Data/TacticInfo.php @@ -2,24 +2,23 @@ namespace IQBall\Core\Data; -class TacticInfo implements \JsonSerializable { +class TacticInfo { private int $id; private string $name; - private int $creation_date; - + private int $creationDate; private int $ownerId; /** * @param int $id * @param string $name - * @param int $creation_date + * @param int $creationDate * @param int $ownerId */ - public function __construct(int $id, string $name, int $creation_date, int $ownerId) { + public function __construct(int $id, string $name, int $creationDate, int $ownerId) { $this->id = $id; $this->name = $name; $this->ownerId = $ownerId; - $this->creation_date = $creation_date; + $this->creationDate = $creationDate; } public function getId(): int { @@ -38,13 +37,7 @@ class TacticInfo implements \JsonSerializable { } public function getCreationTimestamp(): int { - return $this->creation_date; + return $this->creationDate; } - /** - * @return array - */ - public function jsonSerialize(): array { - return get_object_vars($this); - } } diff --git a/src/Core/Data/Team.php b/src/Core/Data/Team.php index 6366680..b8e7834 100755 --- a/src/Core/Data/Team.php +++ b/src/Core/Data/Team.php @@ -3,11 +3,7 @@ namespace IQBall\Core\Data; class Team { - private int $id; - private string $name; - private string $picture; - private Color $mainColor; - private Color $secondColor; + private TeamInfo $info; /** * @var Member[] maps users with their role @@ -15,54 +11,16 @@ class Team { private array $members; /** - * @param string $name - * @param string $picture - * @param Color $mainColor - * @param Color $secondColor + * @param TeamInfo $info * @param Member[] $members */ - public function __construct(int $id, string $name, string $picture, Color $mainColor, Color $secondColor, array $members = []) { - $this->id = $id; - $this->name = $name; - $this->picture = $picture; - $this->mainColor = $mainColor; - $this->secondColor = $secondColor; + public function __construct(TeamInfo $info, array $members = []) { + $this->info = $info; $this->members = $members; } - /** - * @return int - */ - public function getId(): int { - return $this->id; - } - - /** - * @return string - */ - public function getName(): string { - return $this->name; - } - - /** - * @return string - */ - public function getPicture(): string { - return $this->picture; - } - - /** - * @return Color - */ - public function getMainColor(): Color { - return $this->mainColor; - } - - /** - * @return Color - */ - public function getSecondColor(): Color { - return $this->secondColor; + public function getInfo(): TeamInfo { + return $this->info; } /** @@ -71,9 +29,4 @@ class Team { public function listMembers(): array { return $this->members; } - - public function addMember(Member $m): void { - $this->members[] = $m; - } - } diff --git a/src/Core/Data/TeamInfo.php b/src/Core/Data/TeamInfo.php new file mode 100644 index 0000000..7affcea --- /dev/null +++ b/src/Core/Data/TeamInfo.php @@ -0,0 +1,49 @@ +id = $id; + $this->name = $name; + $this->picture = $picture; + $this->mainColor = $mainColor; + $this->secondColor = $secondColor; + } + + + public function getId(): int { + return $this->id; + } + + public function getName(): string { + return $this->name; + } + + public function getPicture(): string { + return $this->picture; + } + + public function getMainColor(): Color { + return $this->mainColor; + } + + public function getSecondColor(): Color { + return $this->secondColor; + } + + +} diff --git a/src/Core/Data/User.php b/src/Core/Data/User.php deleted file mode 100755 index 004379d..0000000 --- a/src/Core/Data/User.php +++ /dev/null @@ -1,26 +0,0 @@ -con->fetch("SELECT * FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]])[0] ?? null; } - + /** + * @param string $email + * @return string|null the hashed user's password, or null if the given mail does not exist + */ public function getHash(string $email): ?string { $results = $this->getRowsFromMail($email); if ($results == null) { @@ -44,6 +47,10 @@ class AccountGateway { return $results['hash']; } + /** + * @param string $email + * @return bool true if the given email exists in the database + */ public function exists(string $email): bool { return $this->getRowsFromMail($email) != null; } @@ -61,6 +68,10 @@ class AccountGateway { return new Account($email, $acc["username"], $acc["token"], $acc["id"]); } + /** + * @param string $token get an account from given token + * @return Account|null + */ public function getAccountFromToken(string $token): ?Account { $acc = $this->con->fetch("SELECT * FROM Account WHERE token = :token", [':token' => [$token, PDO::PARAM_STR]])[0] ?? null; if (empty($acc)) { diff --git a/src/Core/Gateway/AuthGateway.php b/src/Core/Gateway/AuthGateway.php deleted file mode 100644 index a748f39..0000000 --- a/src/Core/Gateway/AuthGateway.php +++ /dev/null @@ -1,47 +0,0 @@ -con = $con; - } - - - public function mailExist(string $email): bool { - return $this->getUserFields($email) != null; - } - - - public function insertAccount(string $username, string $hash, string $email): void { - $this->con->exec("INSERT INTO AccountUser(username, hash, email) VALUES (:username,:hash,:email)", [':username' => [$username, PDO::PARAM_STR],':hash' => [$hash, PDO::PARAM_STR],':email' => [$email, PDO::PARAM_STR]]); - } - - public function getUserHash(string $email): string { - $results = $this->con->fetch("SELECT hash FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); - return $results[0]['hash']; - } - - - /** - * @param string $email - * @return array|null - */ - public function getUserFields(string $email): ?array { - $results = $this->con->fetch("SELECT username,email FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]); - $firstRow = $results[0] ?? null; - return $firstRow; - } - - - - -} diff --git a/src/Core/Gateway/MemberGateway.php b/src/Core/Gateway/MemberGateway.php new file mode 100644 index 0000000..999bf10 --- /dev/null +++ b/src/Core/Gateway/MemberGateway.php @@ -0,0 +1,69 @@ +con = $con; + } + + /** + * insert member to a team + * @param int $idTeam + * @param int $userId + * @param string $role + * @return void + */ + public function insert(int $idTeam, int $userId, string $role): void { + $this->con->exec( + "INSERT INTO Member(id_team, id_user, role) VALUES (:id_team, :id_user, :role)", + [ + ":id_team" => [$idTeam, PDO::PARAM_INT], + ":id_user" => [$userId, PDO::PARAM_INT], + ":role" => [$role, PDO::PARAM_STR], + ] + ); + } + + /** + * @param int $teamId + * @return Member[] + */ + public function getMembersOfTeam(int $teamId): array { + $rows = $this->con->fetch( + "SELECT a.id,m.role,a.email,a.username FROM Account a,Team t,Member m WHERE t.id = :id AND m.id_team = t.id AND m.id_user = a.id", + [ + ":id" => [$teamId, PDO::PARAM_INT], + ] + ); + + return array_map(fn($row) => new Member($row['id_user'], $row['id_team'], MemberRole::fromName($row['role'])), $rows); + } + + /** + * remove member from given team + * @param int $idTeam + * @param int $idMember + * @return void + */ + public function remove(int $idTeam, int $idMember): void { + $this->con->exec( + "DELETE FROM Member WHERE id_team = :id_team AND id_user = :id_user", + [ + ":id_team" => [$idTeam, PDO::PARAM_INT], + ":id_user" => [$idMember, PDO::PARAM_INT], + ] + ); + } + +} diff --git a/src/Core/Gateway/TacticInfoGateway.php b/src/Core/Gateway/TacticInfoGateway.php index 2031d04..f3d2432 100644 --- a/src/Core/Gateway/TacticInfoGateway.php +++ b/src/Core/Gateway/TacticInfoGateway.php @@ -16,6 +16,11 @@ class TacticInfoGateway { $this->con = $con; } + /** + * get tactic information from given identifier + * @param int $id + * @return TacticInfo|null + */ public function get(int $id): ?TacticInfo { $res = $this->con->fetch( "SELECT * FROM Tactic WHERE id = :id", @@ -49,6 +54,11 @@ class TacticInfoGateway { return $res; } + /** + * @param string $name + * @param int $owner + * @return TacticInfo + */ public function insert(string $name, int $owner): TacticInfo { $this->con->exec( "INSERT INTO Tactic(name, owner) VALUES(:name, :owner)", @@ -64,6 +74,12 @@ class TacticInfoGateway { return new TacticInfo(intval($row["id"]), $name, strtotime($row["creation_date"]), $row["owner"]); } + /** + * update name of given tactic identifier + * @param int $id + * @param string $name + * @return void + */ public function updateName(int $id, string $name): void { $this->con->exec( "UPDATE Tactic SET name = :name WHERE id = :id", diff --git a/src/Core/Gateway/TeamGateway.php b/src/Core/Gateway/TeamGateway.php index 942d2a4..d775eda 100644 --- a/src/Core/Gateway/TeamGateway.php +++ b/src/Core/Gateway/TeamGateway.php @@ -3,6 +3,8 @@ namespace IQBall\Core\Gateway; use IQBall\Core\Connection; +use IQBall\Core\Data\Color; +use IQBall\Core\Data\TeamInfo; use PDO; class TeamGateway { @@ -12,62 +14,65 @@ class TeamGateway { $this->con = $con; } - public function insert(string $name, string $picture, string $mainColor, string $secondColor): void { + /** + * @param string $name + * @param string $picture + * @param string $mainColor + * @param string $secondColor + * @return int the inserted team identifier + */ + public function insert(string $name, string $picture, string $mainColor, string $secondColor): int { $this->con->exec( - "INSERT INTO Team(name, picture, mainColor, secondColor) VALUES (:teamName , :picture, :mainColor, :secondColor)", + "INSERT INTO Team(name, picture, main_color, second_color) VALUES (:team_name , :picture, :main_color, :second_color)", [ - ":teamName" => [$name, PDO::PARAM_STR], + ":team_name" => [$name, PDO::PARAM_STR], ":picture" => [$picture, PDO::PARAM_STR], - ":mainColor" => [$mainColor, PDO::PARAM_STR], - ":secondColor" => [$secondColor, PDO::PARAM_STR], - ] - ); - } - - - public function insertMember(int $idTeam, int $idMember, string $role): void { - $this->con->exec( - "INSERT INTO Member(idTeam, idMember, role) VALUES (:idTeam , :idMember, :role)", - [ - ":idTeam" => [$idTeam, PDO::PARAM_INT], - ":idMember" => [$idMember, PDO::PARAM_INT], - ":role" => [$role, PDO::PARAM_STR], + ":main_color" => [$mainColor, PDO::PARAM_STR], + ":second_color" => [$secondColor, PDO::PARAM_STR], ] ); + return intval($this->con->lastInsertId()); } /** * @param string $name - * @return array[] + * @return TeamInfo[] */ public function listByName(string $name): array { - return $this->con->fetch( - "SELECT id,name,picture,mainColor,secondColor FROM Team WHERE name LIKE '%' || :name || '%'", + $result = $this->con->fetch( + "SELECT * FROM Team WHERE name LIKE '%' || :name || '%'", [ ":name" => [$name, PDO::PARAM_STR], ] ); + + return array_map(fn($row) => new TeamInfo($row['id'], $row['name'], $row['picture'], Color::from($row['main_color']), Color::from($row['second_color'])), $result); } /** * @param int $id - * @return array + * @return TeamInfo */ - public function getTeamById(int $id): ?array { - return $this->con->fetch( - "SELECT id,name,picture,mainColor,secondColor FROM Team WHERE id = :id", + public function getTeamById(int $id): ?TeamInfo { + $row = $this->con->fetch( + "SELECT * FROM Team WHERE id = :id", [ ":id" => [$id, PDO::PARAM_INT], ] )[0] ?? null; + if ($row == null) { + return null; + } + + return new TeamInfo($row['id'], $row['name'], $row['picture'], Color::from($row['main_color']), Color::from($row['second_color'])); } /** * @param string $name * @return int|null */ - public function getIdTeamByName(string $name): ?int { + public function getTeamIdByName(string $name): ?int { return $this->con->fetch( "SELECT id FROM Team WHERE name = :name", [ @@ -76,40 +81,5 @@ class TeamGateway { )[0]['id'] ?? null; } - /** - * @param int $id - * @return array[] - */ - public function getMembersById(int $id): array { - return $this->con->fetch( - "SELECT a.id,m.role,a.email,a.username FROM Account a,Team t,Member m WHERE t.id = :id AND m.idTeam = t.id AND m.idMember = a.id", - [ - ":id" => [$id, PDO::PARAM_INT], - ] - ); - } - - /** - * @param string $mail - * @return int|null - */ - public function getMemberIdByMail(string $mail): ?int { - return $this->con->fetch( - "SELECT id FROM Account WHERE email = :mail", - [ - ":mail" => [$mail, PDO::PARAM_STR], - ] - )[0]['id'] ?? null; - } - - public function deleteMember(int $idTeam, int $idMember): void { - $this->con->exec( - "DELETE FROM Member WHERE idTeam = :idTeam AND idMember = :idMember", - [ - ":idTeam" => [$idTeam, PDO::PARAM_INT], - ":idMember" => [$idMember, PDO::PARAM_INT], - ] - ); - } } diff --git a/src/Core/Http/HttpCodes.php b/src/Core/Http/HttpCodes.php index d0a00c5..1903f0c 100644 --- a/src/Core/Http/HttpCodes.php +++ b/src/Core/Http/HttpCodes.php @@ -9,6 +9,7 @@ class HttpCodes { public const OK = 200; public const FOUND = 302; public const BAD_REQUEST = 400; + public const UNAUTHORIZED = 401; public const FORBIDDEN = 403; diff --git a/src/Core/Http/HttpResponse.php b/src/Core/Http/HttpResponse.php index 345b2ab..eb52ccc 100644 --- a/src/Core/Http/HttpResponse.php +++ b/src/Core/Http/HttpResponse.php @@ -29,10 +29,19 @@ class HttpResponse { return $this->headers; } + /** + * @param int $code + * @return HttpResponse + */ public static function fromCode(int $code): HttpResponse { return new HttpResponse($code, []); } + /** + * @param string $url the url to redirect + * @param int $code only HTTP 3XX codes are accepted. + * @return HttpResponse a response that will redirect client to given url + */ public static function redirect(string $url, int $code = HttpCodes::FOUND): HttpResponse { if ($code < 300 || $code >= 400) { throw new \InvalidArgumentException("given code is not a redirection http code"); diff --git a/src/Core/Model/AuthModel.php b/src/Core/Model/AuthModel.php index 9044785..a6ada53 100644 --- a/src/Core/Model/AuthModel.php +++ b/src/Core/Model/AuthModel.php @@ -47,6 +47,10 @@ class AuthModel { return new Account($email, $username, $token, $accountId); } + /** + * Generate a random base 64 string + * @return string + */ private function generateToken(): string { return base64_encode(random_bytes(64)); } @@ -58,11 +62,11 @@ class AuthModel { * @return Account|null the authenticated account or null if failures occurred */ public function login(string $email, string $password, array &$failures): ?Account { - if (!$this->gateway->exists($email)) { - $failures[] = new FieldValidationFail("email", "Vous n'êtes pas enregistré."); + $hash = $this->gateway->getHash($email); + if ($hash == null) { + $failures[] = new FieldValidationFail("email", "l'addresse email n'est pas connue."); return null; } - $hash = $this->gateway->getHash($email); if (!password_verify($password, $hash)) { $failures[] = new FieldValidationFail("password", "Mot de passe invalide."); diff --git a/src/Core/Model/TacticModel.php b/src/Core/Model/TacticModel.php index a74685a..64c6ca3 100644 --- a/src/Core/Model/TacticModel.php +++ b/src/Core/Model/TacticModel.php @@ -19,12 +19,23 @@ class TacticModel { $this->tactics = $tactics; } + /** + * creates a new empty tactic, with given name + * @param string $name + * @param int $ownerId + * @return TacticInfo + */ public function makeNew(string $name, int $ownerId): TacticInfo { return $this->tactics->insert($name, $ownerId); } + /** + * creates a new empty tactic, with a default name + * @param int $ownerId + * @return TacticInfo|null + */ public function makeNewDefault(int $ownerId): ?TacticInfo { - return $this->tactics->insert(self::TACTIC_DEFAULT_NAME, $ownerId); + return $this->makeNew(self::TACTIC_DEFAULT_NAME, $ownerId); } /** diff --git a/src/Core/Model/TeamModel.php b/src/Core/Model/TeamModel.php index fc2afc8..f6af837 100644 --- a/src/Core/Model/TeamModel.php +++ b/src/Core/Model/TeamModel.php @@ -2,65 +2,80 @@ namespace IQBall\Core\Model; -use IQBall\Core\Gateway\TeamGateway; -use IQBall\Core\Data\Team; -use IQBall\Core\Data\Member; -use IQBall\Core\Data\MemberRole; use IQBall\Core\Data\Color; +use IQBall\Core\Data\Team; +use IQBall\Core\Data\TeamInfo; +use IQBall\Core\Gateway\AccountGateway; +use IQBall\Core\Gateway\MemberGateway; +use IQBall\Core\Gateway\TeamGateway; class TeamModel { - private TeamGateway $gateway; + private AccountGateway $users; + private TeamGateway $teams; + private MemberGateway $members; /** * @param TeamGateway $gateway + * @param MemberGateway $members + * @param AccountGateway $users */ - public function __construct(TeamGateway $gateway) { - $this->gateway = $gateway; + public function __construct(TeamGateway $gateway, MemberGateway $members, AccountGateway $users) { + $this->teams = $gateway; + $this->members = $members; + $this->users = $users; } + /** + * @param string $name + * @param string $picture + * @param string $mainColor + * @param string $secondColor + * @return int + */ public function createTeam(string $name, string $picture, string $mainColor, string $secondColor): int { - $this->gateway->insert($name, $picture, $mainColor, $secondColor); - return $this->gateway->getIdTeamByName($name); + return $this->teams->insert($name, $picture, $mainColor, $secondColor); } - public function addMember(string $mail, int $teamId, string $role): int { - $id = $this->gateway->getMemberIdByMail($mail); - $this->gateway->insertMember($teamId, $id, $role); - return $teamId; + /** + * adds a member to a team + * @param string $mail + * @param int $teamId + * @param string $role + * @return void + */ + public function addMember(string $mail, int $teamId, string $role): void { + $userId = $this->users->getAccountFromMail($mail)->getId(); + $this->members->insert($teamId, $userId, $role); } /** * @param string $name - * @return Team[] + * @return TeamInfo[] */ public function listByName(string $name): array { - $teams = []; - $results = $this->gateway->listByName($name); - foreach ($results as $row) { - $teams[] = new Team($row['id'], $row['name'], $row['picture'], Color::from($row['mainColor']), Color::from($row['secondColor'])); - } - return $teams; + return $this->teams->listByName($name); } - public function displayTeam(int $id): Team { - $members = []; - $result = $this->gateway->getTeamById($id); - $resultMembers = $this->gateway->getMembersById($id); - foreach ($resultMembers as $row) { - var_dump($row['role']); - if ($row['role'] == 'C') { - $role = MemberRole::coach(); - } else { - $role = MemberRole::player(); - } - $members[] = new Member($row['id'], $id, $role); - } - return new Team(intval($result['id']), $result['name'], $result['picture'], Color::from($result['mainColor']), Color::from($result['secondColor']), $members); + /** + * @param int $id + * @return Team + */ + public function getTeam(int $id): Team { + $teamInfo = $this->teams->getTeamById($id); + $members = $this->members->getMembersOfTeam($id); + return new Team($teamInfo, $members); } + + /** + * delete a member from given team identifier + * @param string $mail + * @param int $teamId + * @return int + */ public function deleteMember(string $mail, int $teamId): int { - $memberId = $this->gateway->getMemberIdByMail($mail); - $this->gateway->deleteMember($teamId, $memberId); + $userId = $this->users->getAccountFromMail($mail)->getId(); + $this->members->remove($teamId, $userId); return $teamId; } diff --git a/src/Core/Validation/ValidationFail.php b/src/Core/Validation/ValidationFail.php index 01b3473..9a714e5 100644 --- a/src/Core/Validation/ValidationFail.php +++ b/src/Core/Validation/ValidationFail.php @@ -33,10 +33,18 @@ class ValidationFail implements JsonSerializable { return ["error" => $this->kind, "message" => $this->message]; } + /** + * @param string $message + * @return ValidationFail validation fail for unknown resource access + */ public static function notFound(string $message): ValidationFail { return new ValidationFail("Not found", $message); } + /** + * @param string $message + * @return ValidationFail validation fail for unauthorized accesses + */ public static function unauthorized(string $message = "Unauthorized"): ValidationFail { return new ValidationFail("Unauthorized", $message); } diff --git a/src/Core/Validation/Validators.php b/src/Core/Validation/Validators.php index b64d704..52bc08c 100644 --- a/src/Core/Validation/Validators.php +++ b/src/Core/Validation/Validators.php @@ -12,10 +12,18 @@ class Validators { public static function regex(string $regex, ?string $msg = null): Validator { return new SimpleFunctionValidator( fn(string $str) => preg_match($regex, $str), - fn(string $name) => [new FieldValidationFail($name, $msg == null ? "field does not validates pattern $regex" : $msg)] + fn(string $name) => [new FieldValidationFail($name, $msg == null ? "le champ ne valide pas le pattern $regex" : $msg)] ); } + public static function hex(?string $msg = null): Validator { + return self::regex('/#([0-9a-fA-F])/', $msg == null ? "le champ n'est pas un nombre hexadecimal valide" : $msg); + } + + public static function hexColor(?string $msg = null): Validator { + return self::regex('/#([0-9a-fA-F]{6})/', $msg == null ? "le champ n'est pas une couleur valide" : $msg); + } + /** * @return Validator a validator that validates strings that only contains numbers, letters, accents letters, `-` and `_`. */ @@ -51,6 +59,13 @@ class Validators { ); } + public static function email(?string $msg = null): Validator { + return new SimpleFunctionValidator( + fn(string $str) => filter_var($str, FILTER_VALIDATE_EMAIL), + fn(string $name) => [new FieldValidationFail($name, $msg == null ? "addresse mail invalide" : $msg)] + ); + } + public static function isInteger(): Validator { return self::regex("/^[0-9]+$/"); diff --git a/src/Core/Validator/TacticValidator.php b/src/Core/Validator/TacticValidator.php index 9c5ba11..6c9a57b 100644 --- a/src/Core/Validator/TacticValidator.php +++ b/src/Core/Validator/TacticValidator.php @@ -6,6 +6,12 @@ use IQBall\Core\Data\TacticInfo; use IQBall\Core\Validation\ValidationFail; class TacticValidator { + /** + * @param TacticInfo|null $tactic + * @param int $tacticId + * @param int $ownerId + * @return ValidationFail|null + */ public static function validateAccess(?TacticInfo $tactic, int $tacticId, int $ownerId): ?ValidationFail { if ($tactic == null) { return ValidationFail::notFound("La tactique $tacticId n'existe pas"); @@ -17,5 +23,4 @@ class TacticValidator { return null; } - }