Merge Wednesday Rush and remove Salva's imposed conception aberrations #22
Merged
maxime.batista
merged 48 commits from salva
into master
1 year ago
@ -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
|
After Width: | Height: | Size: 507 B |
After Width: | Height: | Size: 732 B |
@ -1,95 +1,109 @@
|
||||
<?php
|
||||
|
||||
|
||||
require "../vendor/autoload.php";
|
||||
require "../config.php";
|
||||
require "../sql/database.php";
|
||||
require "utils.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);
|
||||
require "../src/utils.php";
|
||||
require "../src/App/react-display.php";
|
||||
|
||||
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\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());
|
||||
}
|
||||
|
||||
$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;
|
||||
function getUserController(): UserController {
|
||||
return new UserController(new TacticModel(new TacticInfoGateway(getConnection())));
|
||||
}
|
||||
|
||||
function getVisualizerController(): VisualizerController {
|
||||
return new VisualizerController(new TacticModel(new TacticInfoGateway(getConnection())));
|
||||
}
|
||||
|
||||
function getEditorController(): EditorController {
|
||||
return new EditorController(new TacticModel(new TacticInfoGateway(getConnection())));
|
||||
}
|
||||
|
||||
function getTeamController(): TeamController {
|
||||
$con = getConnection();
|
||||
return new TeamController(new TeamModel(new TeamGateway($con), new MemberGateway($con), new AccountGateway($con)));
|
||||
}
|
||||
|
||||
$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;
|
||||
function getAuthController(): AuthController {
|
||||
return new AuthController(new AuthModel(new AccountGateway(getConnection())));
|
||||
}
|
||||
|
||||
function getRoutes(): AltoRouter {
|
||||
global $basePath;
|
||||
|
||||
$ar = new AltoRouter();
|
||||
$ar->setBasePath($basePath);
|
||||
|
||||
//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()->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)));
|
||||
$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", 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
|
||||
$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 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);
|
||||
}
|
||||
|
||||
} elseif ($response instanceof JsonHttpResponse) {
|
||||
header('Content-type: application/json');
|
||||
echo $response->getJson();
|
||||
return App::runAction($basePath . '/login', $match['target'], $match['params'], $session);
|
||||
}
|
||||
|
||||
|
||||
//this is a global variable
|
||||
$basePath = get_public_path();
|
||||
|
||||
App::render(runMatch(getRoutes()->match(), PhpSessionHandle::init()), "../src/App/Views/");
|
||||
|
@ -1,40 +1,44 @@
|
||||
-- drop tables here
|
||||
DROP TABLE IF EXISTS FormEntries;
|
||||
DROP TABLE IF EXISTS AccountUser;
|
||||
DROP TABLE IF EXISTS TacticInfo;
|
||||
DROP TABLE IF EXISTS Account;
|
||||
DROP TABLE IF EXISTS Tactic;
|
||||
DROP TABLE IF EXISTS Team;
|
||||
DROP TABLE IF EXISTS User;
|
||||
DROP TABLE IF EXISTS Member;
|
||||
|
||||
CREATE TABLE FormEntries(name varchar, description varchar);
|
||||
CREATE TABLE AccountUser(
|
||||
username varchar,
|
||||
hash varchar,
|
||||
email varchar unique
|
||||
CREATE TABLE Account
|
||||
(
|
||||
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 Team(
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
name varchar,
|
||||
picture varchar,
|
||||
mainColor varchar,
|
||||
secondColor varchar
|
||||
CREATE TABLE Tactic
|
||||
(
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
name varchar NOT NULL,
|
||||
creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
owner integer NOT NULL,
|
||||
FOREIGN KEY (owner) REFERENCES Account
|
||||
);
|
||||
|
||||
CREATE TABLE User(
|
||||
id integer PRIMARY KEY AUTOINCREMENT
|
||||
);
|
||||
CREATE TABLE FormEntries(name varchar, description 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)
|
||||
|
||||
CREATE TABLE Team
|
||||
(
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
name varchar,
|
||||
picture varchar,
|
||||
main_color varchar,
|
||||
second_color varchar
|
||||
);
|
||||
|
||||
CREATE TABLE TacticInfo(
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
name varchar,
|
||||
creation_date timestamp DEFAULT CURRENT_TIMESTAMP
|
||||
|
||||
CREATE TABLE Member(
|
||||
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)
|
||||
);
|
||||
|
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Api;
|
||||
|
||||
use Exception;
|
||||
use IQBall\Core\Action;
|
||||
use IQBall\Core\Connection;
|
||||
use IQBall\Core\Data\Account;
|
||||
use IQBall\Core\Gateway\AccountGateway;
|
||||
use IQBall\Core\Http\HttpResponse;
|
||||
use IQBall\Core\Http\JsonHttpResponse;
|
||||
use IQBall\Core\Session\PhpSessionHandle;
|
||||
use IQBall\Core\Validation\ValidationFail;
|
||||
|
||||
class API {
|
||||
public static 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();
|
||||
} 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
|||||||
<?php
|
|||||||
|
|||||||
namespace IQBall\Api\Controller;
|
|||||||
|
|||||||
use IQBall\Core\Http\HttpCodes;
|
|||||||
use IQBall\Core\Route\Control;
|
|||||||
use IQBall\Core\Http\HttpRequest;
|
|||||||
use IQBall\Core\Http\HttpResponse;
|
|||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|||||||
use IQBall\Core\Model\AuthModel;
|
|||||||
use IQBall\Core\Validation\Validators;
|
|||||||
|
|||||||
class APIAuthController {
|
|||||||
private AuthModel $model;
|
|||||||
|
|||||||
/**
|
|||||||
* @param AuthModel $model
|
|||||||
*/
|
|||||||
public function __construct(AuthModel $model) {
|
|||||||
$this->model = $model;
|
|||||||
}
|
|||||||
|
|||||||
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
/**
|
|||||||
* 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::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, HttpCodes::UNAUTHORIZED);
|
|||||||
}
|
|||||||
|
|||||||
return new JsonHttpResponse(["authorization" => $account->getToken()]);
|
|||||||
}, true);
|
|||||||
}
|
|||||||
|
|||||||
}
|
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Api\Controller;
|
||||
|
||||
use IQBall\Core\Route\Control;
|
||||
use IQBall\Core\Data\Account;
|
||||
use IQBall\Core\Http\HttpCodes;
|
||||
use IQBall\Core\Http\HttpRequest;
|
||||
use IQBall\Core\Http\HttpResponse;
|
||||
use IQBall\Core\Http\JsonHttpResponse;
|
||||
use IQBall\Core\Model\TacticModel;
|
||||
use IQBall\Core\Validation\Validators;
|
||||
|
||||
/**
|
||||
* API endpoint related to tactics
|
||||
*/
|
||||
class APITacticController {
|
||||
private TacticModel $model;
|
||||
|
||||
/**
|
||||
* @param TacticModel $model
|
||||
*/
|
||||
public function __construct(TacticModel $model) {
|
||||
$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()],
|
||||
], 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
|||||||
<?php
|
|||||||
|
|||||||
namespace IQBall\App;
|
|||||||
|
|||||||
use IQBall\Core\Action;
|
|||||||
use IQBall\Core\Http\HttpResponse;
|
|||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|||||||
use IQBall\Core\Session\MutableSessionHandle;
|
|||||||
use Twig\Environment;
|
|||||||
use Twig\Error\LoaderError;
|
|||||||
use Twig\Error\RuntimeError;
|
|||||||
use Twig\Error\SyntaxError;
|
|||||||
use Twig\Loader\FilesystemLoader;
|
|||||||
|
|||||||
class App {
|
|||||||
/**
|
|||||||
* 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) {
|
|||||||
header("$header: $value");
|
|||||||
}
|
|||||||
|
|||||||
if ($response instanceof ViewHttpResponse) {
|
|||||||
self::renderView($response, $twigViewsFolder);
|
|||||||
} elseif ($response instanceof JsonHttpResponse) {
|
|||||||
header('Content-type: application/json');
|
|||||||
echo $response->getJson();
|
|||||||
}
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* 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();
|
|||||||
|
|||||||
switch ($response->getViewKind()) {
|
|||||||
case ViewHttpResponse::REACT_VIEW:
|
|||||||
send_react_front($file, $args);
|
|||||||
break;
|
|||||||
case ViewHttpResponse::TWIG_VIEW:
|
|||||||
try {
|
|||||||
$fl = new FilesystemLoader($twigViewsFolder);
|
|||||||
$twig = new Environment($fl);
|
|||||||
$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;
|
|||||||
}
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* 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<MutableSessionHandle> $action
|
|||||||
* @param mixed[] $params
|
|||||||
* @param MutableSessionHandle $session
|
|||||||
* @return HttpResponse
|
|||||||
*/
|
|||||||
public static function runAction(string $authRoute, Action $action, array $params, MutableSessionHandle $session): HttpResponse {
|
|||||||
if ($action->isAuthRequired()) {
|
|||||||
$account = $session->getAccount();
|
|||||||
if ($account == null) {
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
// put in the session the initial url the user wanted to get
|
|||||||
$session->setInitialTarget($_SERVER['REQUEST_URI']);
|
|||||||
return HttpResponse::redirect($authRoute);
|
|||||||
}
|
|||||||
}
|
|||||||
|
|||||||
return $action->run($params, $session);
|
|||||||
}
|
|||||||
|
|||||||
}
|
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\App\Controller;
|
||||
|
||||
use IQBall\Core\Http\HttpRequest;
|
||||
use IQBall\Core\Http\HttpResponse;
|
||||
use IQBall\App\ViewHttpResponse;
|
||||
use IQBall\Core\Model\AuthModel;
|
||||
use IQBall\Core\Session\MutableSessionHandle;
|
||||
use IQBall\Core\Validation\ValidationFail;
|
||||
use IQBall\Core\Validation\Validators;
|
||||
|
||||
class AuthController {
|
||||
private AuthModel $model;
|
||||
|
||||
/**
|
||||
* @param AuthModel $model
|
||||
*/
|
||||
public function __construct(AuthModel $model) {
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
public function displayRegister(): HttpResponse {
|
||||
return ViewHttpResponse::twig("display_register.html.twig", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* registers given account
|
||||
* @param mixed[] $request
|
||||
* @param MutableSessionHandle $session
|
||||
* @return 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::email(), Validators::lenBetween(5, 256)],
|
||||
]);
|
||||
if (!empty($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 ViewHttpResponse::twig("display_register.html.twig", ['fails' => $fails]);
|
||||
}
|
||||
|
||||
$session->setAccount($account);
|
||||
|
||||
$target_url = $session->getInitialTarget();
|
||||
return HttpResponse::redirect($target_url ?? "/home");
|
||||
}
|
||||
|
||||
|
||||
public function displayLogin(): HttpResponse {
|
||||
return ViewHttpResponse::twig("display_login.html.twig", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* logins given account credentials
|
||||
* @param mixed[] $request
|
||||
* @param MutableSessionHandle $session
|
||||
* @return HttpResponse
|
||||
*/
|
||||
public function login(array $request, MutableSessionHandle $session): HttpResponse {
|
||||
$fails = [];
|
||||
$request = HttpRequest::from($request, $fails, [
|
||||
"password" => [Validators::lenBetween(6, 256)],
|
||||
"email" => [Validators::email(), Validators::lenBetween(5, 256)],
|
||||
]);
|
||||
if (!empty($fails)) {
|
||||
return ViewHttpResponse::twig("display_login.html.twig", ['fails' => $fails]);
|
||||
}
|
||||
|
||||
$account = $this->model->login($request['email'], $request['password'], $fails);
|
||||
if (!empty($fails)) {
|
||||
return ViewHttpResponse::twig("display_login.html.twig", ['fails' => $fails]);
|
||||
}
|
||||
|
||||
$session->setAccount($account);
|
||||
|
||||
$target_url = $session->getInitialTarget();
|
||||
$session->setInitialTarget(null);
|
||||
return HttpResponse::redirect($target_url ?? "/home");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\App\Controller;
|
||||
|
||||
use IQBall\Core\Data\TacticInfo;
|
||||
use IQBall\Core\Http\HttpCodes;
|
||||
use IQBall\Core\Http\HttpResponse;
|
||||
use IQBall\App\ViewHttpResponse;
|
||||
use IQBall\Core\Model\TacticModel;
|
||||
use IQBall\Core\Session\SessionHandle;
|
||||
use IQBall\Core\Validator\TacticValidator;
|
||||
|
||||
class EditorController {
|
||||
private TacticModel $model;
|
||||
|
||||
public function __construct(TacticModel $model) {
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->openEditorFor($tactic);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an editor view for a given tactic
|
||||
* @param int $id the targeted tactic identifier
|
||||
* @param SessionHandle $session
|
||||
* @return ViewHttpResponse
|
||||
*/
|
||||
public function openEditor(int $id, SessionHandle $session): ViewHttpResponse {
|
||||
$tactic = $this->model->get($id);
|
||||
|
||||
$failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId());
|
||||
|
||||
if ($failure != null) {
|
||||
return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND);
|
||||
}
|
||||
|
||||
return $this->openEditorFor($tactic);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,155 @@
|
|||||||
<?php
|
|||||||
|
|||||||
namespace IQBall\App\Controller;
|
|||||||
|
|||||||
use IQBall\Core\Http\HttpRequest;
|
|||||||
use IQBall\Core\Http\HttpResponse;
|
|||||||
use IQBall\App\ViewHttpResponse;
|
|||||||
use IQBall\Core\Model\TeamModel;
|
|||||||
use IQBall\Core\Session\SessionHandle;
|
|||||||
use IQBall\Core\Validation\FieldValidationFail;
|
|||||||
use IQBall\Core\Validation\Validators;
|
|||||||
|
|||||||
class TeamController {
|
|||||||
private TeamModel $model;
|
|||||||
|
|||||||
/**
|
|||||||
* @param TeamModel $model
|
|||||||
*/
|
|||||||
public function __construct(TeamModel $model) {
|
|||||||
$this->model = $model;
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* @param SessionHandle $session
|
|||||||
* @return ViewHttpResponse the team creation panel
|
|||||||
*/
|
|||||||
public function displayCreateTeam(SessionHandle $session): ViewHttpResponse {
|
|||||||
return ViewHttpResponse::twig("insert_team.html.twig", []);
|
|||||||
}
|
|||||||
|
|||||||
|
|||||||
/**
|
|||||||
* @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", []);
|
|||||||
}
|
|||||||
|
|||||||
|
|||||||
/**
|
|||||||
* @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<string, mixed> $request
|
|||||||
* @param SessionHandle $session
|
|||||||
* @return HttpResponse
|
|||||||
*/
|
|||||||
public function submitTeam(array $request, SessionHandle $session): HttpResponse {
|
|||||||
$failures = [];
|
|||||||
$request = HttpRequest::from($request, $failures, [
|
|||||||
"name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()],
|
|||||||
"main_color" => [Validators::hexColor()],
|
|||||||
"second_color" => [Validators::hexColor()],
|
|||||||
"picture" => [Validators::isURL()],
|
|||||||
]);
|
|||||||
if (!empty($failures)) {
|
|||||||
$badFields = [];
|
|||||||
foreach ($failures as $e) {
|
|||||||
if ($e instanceof FieldValidationFail) {
|
|||||||
$badFields[] = $e->getFieldName();
|
|||||||
}
|
|||||||
}
|
|||||||
return ViewHttpResponse::twig('insert_team.html.twig', ['bad_fields' => $badFields]);
|
|||||||
}
|
|||||||
$teamId = $this->model->createTeam($request['name'], $request['picture'], $request['main_color'], $request['second_color']);
|
|||||||
return $this->displayTeam($teamId, $session);
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* @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", []);
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* returns a view that contains all the teams description whose name matches the given name needle.
|
|||||||
* @param array<string, mixed> $request
|
|||||||
* @param SessionHandle $session
|
|||||||
* @return HttpResponse
|
|||||||
*/
|
|||||||
public function listTeamByName(array $request, SessionHandle $session): HttpResponse {
|
|||||||
$errors = [];
|
|||||||
$request = HttpRequest::from($request, $errors, [
|
|||||||
"name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()],
|
|||||||
]);
|
|||||||
|
|||||||
if (!empty($errors) && $errors[0] instanceof FieldValidationFail) {
|
|||||||
$badField = $errors[0]->getFieldName();
|
|||||||
return ViewHttpResponse::twig('list_team_by_name.html.twig', ['bad_field' => $badField]);
|
|||||||
}
|
|||||||
|
|||||||
$teams = $this->model->listByName($request['name']);
|
|||||||
|
|||||||
if (empty($teams)) {
|
|||||||
return ViewHttpResponse::twig('display_teams.html.twig', []);
|
|||||||
}
|
|||||||
|
|||||||
return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $teams]);
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* @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]);
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
* add a member to a team
|
|||||||
* @param array<string, mixed> $request
|
|||||||
* @param SessionHandle $session
|
|||||||
* @return HttpResponse
|
|||||||
*/
|
|||||||
public function addMember(array $request, SessionHandle $session): HttpResponse {
|
|||||||
$errors = [];
|
|||||||
|
|||||||
$request = HttpRequest::from($request, $errors, [
|
|||||||
"team" => [Validators::isInteger()],
|
|||||||
"email" => [Validators::email(), Validators::lenBetween(5, 256)],
|
|||||||
]);
|
|||||||
|
|||||||
$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<string, mixed> $request
|
|||||||
* @param SessionHandle $session
|
|||||||
* @return HttpResponse
|
|||||||
*/
|
|||||||
public function deleteMember(array $request, SessionHandle $session): HttpResponse {
|
|||||||
$errors = [];
|
|||||||
|
|||||||
$request = HttpRequest::from($request, $errors, [
|
|||||||
"team" => [Validators::isInteger()],
|
|||||||
"email" => [Validators::email(), Validators::lenBetween(5, 256)],
|
|||||||
]);
|
|||||||
|
|||||||
return $this->displayTeam($this->model->deleteMember($request['email'], intval($request['team'])), $session);
|
|||||||
}
|
|||||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\App\Controller;
|
||||
|
||||
use IQBall\Core\Http\HttpResponse;
|
||||
use IQBall\App\ViewHttpResponse;
|
||||
use IQBall\Core\Model\TacticModel;
|
||||
use IQBall\Core\Session\SessionHandle;
|
||||
|
||||
class UserController {
|
||||
private TacticModel $tactics;
|
||||
|
||||
/**
|
||||
* @param TacticModel $tactics
|
||||
*/
|
||||
public function __construct(TacticModel $tactics) {
|
||||
$this->tactics = $tactics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SessionHandle $session
|
||||
* @return ViewHttpResponse the home page view
|
||||
*/
|
||||
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 ViewHttpResponse account settings page
|
||||
*/
|
||||
public function settings(SessionHandle $session): ViewHttpResponse {
|
||||
return ViewHttpResponse::twig("account_settings.twig", []);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\App\Controller;
|
||||
|
||||
use IQBall\Core\Http\HttpCodes;
|
||||
use IQBall\Core\Http\HttpResponse;
|
||||
use IQBall\App\ViewHttpResponse;
|
||||
use IQBall\Core\Model\TacticModel;
|
||||
use IQBall\Core\Session\SessionHandle;
|
||||
use IQBall\Core\Validator\TacticValidator;
|
||||
|
||||
class VisualizerController {
|
||||
private TacticModel $tacticModel;
|
||||
|
||||
/**
|
||||
* @param TacticModel $tacticModel
|
||||
*/
|
||||
public function __construct(TacticModel $tacticModel) {
|
||||
$this->tacticModel = $tacticModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
|
||||
if ($failure != null) {
|
||||
return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND);
|
||||
}
|
||||
|
||||
return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Paramètres</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding-left: 10%;
|
||||
padding-right: 10%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<button onclick="location.pathname='/home'">Retour</button>
|
||||
<h1>Paramètres</h1>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ajouter un membre</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="radio"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="submit"]:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.role{
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.radio{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h2>Ajouter un membre à votre équipe</h2>
|
||||
<form action="/team/members/add" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="team">Team où ajouter le membre :</label>
|
||||
<input type="text" id="team" name="team" required>
|
||||
<label for="mail">Email du membre :</label>
|
||||
<input type="text" id="mail" name="mail" required>
|
||||
|
||||
<fieldset class="role">
|
||||
<legend >Rôle du membre dans l'équipe :</legend>
|
||||
<div class="radio">
|
||||
<label for="P">Joueur</label>
|
||||
<input type="radio" id="P" name="role" value="P" checked />
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label for="C">Coach</label>
|
||||
<input type="radio" id="C" name="role" value="C" />
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Confirmer">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ajouter un membre</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="submit"]:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h2>Supprimez un membre de votre équipe</h2>
|
||||
<form action="/team/members/remove" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="team">Team où supprimer le membre :</label>
|
||||
<input type="text" id="team" name="team" required>
|
||||
<label for="mail">Email du membre :</label>
|
||||
<input type="text" id="mail" name="mail" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Confirmer">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,94 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Page d'accueil</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
padding-left: 10%;
|
||||
padding-right: 10%;
|
||||
}
|
||||
|
||||
#bandeau {
|
||||
display : flex;
|
||||
flex-direction : row;
|
||||
}
|
||||
|
||||
#bandeau > h1 {
|
||||
self-align : center;
|
||||
padding : 0%;
|
||||
margin : 0%;
|
||||
justify-content : center;
|
||||
}
|
||||
|
||||
#account {
|
||||
display : flex;
|
||||
flex-direction : column;
|
||||
align-content : center;
|
||||
}
|
||||
|
||||
#account:hover {
|
||||
background-color : gray;
|
||||
}
|
||||
|
||||
#account img {
|
||||
width : 70%;
|
||||
height : auto;
|
||||
align-self : center;
|
||||
padding : 5%;
|
||||
margin : 0%;
|
||||
}
|
||||
|
||||
#account p {
|
||||
align-self : center;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="bandeau">
|
||||
<h1>IQ Ball</h1>
|
||||
<div id="account" onclick="location.pathname='/settings'">
|
||||
<img
|
||||
src="../../../front/assets/icon/account.svg"
|
||||
alt="Account logo"
|
||||
/>
|
||||
<p>Mon profil<p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Mes équipes</h2>
|
||||
|
||||
<button onclick="location.pathname='/team/new'"> Créer une nouvelle équipe </button>
|
||||
|
||||
{% if recentTeam != null %}
|
||||
{% for team in recentTeam %}
|
||||
<div>
|
||||
<p> {{team.name}} </p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>Aucune équipe créé !</p>
|
||||
{% endif %}
|
||||
|
||||
<h2> Mes strategies </h2>
|
||||
|
||||
<button onclick="location.pathname='/tactic/new'"> Créer une nouvelle tactique </button>
|
||||
|
||||
{% if recentTactic != null %}
|
||||
{% for tactic in recentTactic %}
|
||||
<div onclick="location.pathname=/tactic/{{ strategie.id }}/edit">
|
||||
<p> {{tactic.id}} - {{tactic.name}} - {{tactic.creation_date}} </p>
|
||||
<button onclick="location.pathname='/tactic/{{ tactic.id }}/edit'"> Editer la stratégie {{tactic.id}} </button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p> Aucune tactique créé !</p>
|
||||
{% endif %}
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Api;
|
||||
|
||||
use App\Controller\Control;
|
||||
use App\Http\HttpCodes;
|
||||
use App\Http\HttpRequest;
|
||||
use App\Http\HttpResponse;
|
||||
use App\Http\JsonHttpResponse;
|
||||
use App\Model\TacticModel;
|
||||
use App\Validation\Validators;
|
||||
|
||||
/**
|
||||
* API endpoint related to tactics
|
||||
*/
|
||||
class APITacticController {
|
||||
private TacticModel $model;
|
||||
|
||||
/**
|
||||
* @param TacticModel $model
|
||||
*/
|
||||
public function __construct(TacticModel $model) {
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
public function updateName(int $tactic_id): HttpResponse {
|
||||
return Control::runChecked([
|
||||
"name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()],
|
||||
], function (HttpRequest $request) use ($tactic_id) {
|
||||
$this->model->updateName($tactic_id, $request["name"]);
|
||||
return HttpResponse::fromCode(HttpCodes::OK);
|
||||
}, true);
|
||||
}
|
||||
|
||||
public function newTactic(): HttpResponse {
|
||||
return Control::runChecked([
|
||||
"name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()],
|
||||
], function (HttpRequest $request) {
|
||||
$tactic = $this->model->makeNew($request["name"]);
|
||||
$id = $tactic->getId();
|
||||
return new JsonHttpResponse(["id" => $id]);
|
||||
}, true);
|
||||
}
|
||||
|
||||
public function getTacticInfo(int $id): 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);
|
||||
}
|
||||
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Gateway\AuthGateway;
|
||||
use App\Http\HttpRequest;
|
||||
use App\Http\HttpResponse;
|
||||
use App\Http\ViewHttpResponse;
|
||||
use App\Model\AuthModel;
|
||||
use App\Validation\FieldValidationFail;
|
||||
use App\Validation\ValidationFail;
|
||||
use App\Validation\Validators;
|
||||
use Twig\Environment;
|
||||
|
||||
class AuthController {
|
||||
private AuthModel $model;
|
||||
|
||||
/**
|
||||
* @param AuthModel $model
|
||||
*/
|
||||
public function __construct(AuthModel $model) {
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
public function displayRegister(): HttpResponse {
|
||||
return ViewHttpResponse::twig("display_register.html.twig", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $viewName
|
||||
* @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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $request
|
||||
* @return HttpResponse
|
||||
*/
|
||||
public function confirmRegister(array $request): 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+$/"),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']]);
|
||||
}
|
||||
return $this->displayBadFields("display_register.html.twig", $fails);
|
||||
}
|
||||
|
||||
|
||||
public function displayLogin(): HttpResponse {
|
||||
return ViewHttpResponse::twig("display_login.html.twig", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $request
|
||||
* @return HttpResponse
|
||||
*/
|
||||
public function confirmLogin(array $request): HttpResponse {
|
||||
$fails = [];
|
||||
$request = HttpRequest::from($request, $fails, [
|
||||
"password" => [Validators::lenBetween(6, 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']]);
|
||||
}
|
||||
return $this->displayBadFields("display_login.html.twig", $fails);
|
||||
}
|
||||
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Data\TacticInfo;
|
||||
use App\Http\HttpCodes;
|
||||
use App\Http\HttpRequest;
|
||||
use App\Http\HttpResponse;
|
||||
use App\Http\JsonHttpResponse;
|
||||
use App\Http\ViewHttpResponse;
|
||||
use App\Model\TacticModel;
|
||||
|
||||
class EditorController {
|
||||
private TacticModel $model;
|
||||
|
||||
/**
|
||||
* @param TacticModel $model
|
||||
*/
|
||||
public function __construct(TacticModel $model) {
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
private function openEditor(TacticInfo $tactic): HttpResponse {
|
||||
return ViewHttpResponse::react("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]);
|
||||
}
|
||||
|
||||
public function makeNew(): HttpResponse {
|
||||
$tactic = $this->model->makeNewDefault();
|
||||
return $this->openEditor($tactic);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an editor view for a given tactic
|
||||
* @param int $id the targeted tactic identifier
|
||||
* @return HttpResponse
|
||||
*/
|
||||
public function openEditorFor(int $id): HttpResponse {
|
||||
$tactic = $this->model->get($id);
|
||||
|
||||
if ($tactic == null) {
|
||||
return new JsonHttpResponse("la tactique " . $id . " n'existe pas", HttpCodes::NOT_FOUND);
|
||||
}
|
||||
|
||||
return $this->openEditor($tactic);
|
||||
}
|
||||
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
require_once __DIR__ . "/../react-display.php";
|
||||
|
||||
use App\Validation\ValidationFail;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
|
||||
class ErrorController {
|
||||
/**
|
||||
* @param ValidationFail[] $failures
|
||||
* @param Environment $twig
|
||||
* @return void
|
||||
*/
|
||||
public static function displayFailures(array $failures, Environment $twig): void {
|
||||
try {
|
||||
$twig->display("error.html.twig", ['failures' => $failures]);
|
||||
} catch (LoaderError|RuntimeError|SyntaxError $e) {
|
||||
echo "Twig error: $e";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
require_once __DIR__ . "/../react-display.php";
|
||||
|
||||
use App\Gateway\FormResultGateway;
|
||||
use App\Http\HttpRequest;
|
||||
use App\Http\HttpResponse;
|
||||
use App\Http\ViewHttpResponse;
|
||||
use App\Validation\Validators;
|
||||
|
||||
class SampleFormController {
|
||||
private FormResultGateway $gateway;
|
||||
|
||||
/**
|
||||
* @param FormResultGateway $gateway
|
||||
*/
|
||||
public function __construct(FormResultGateway $gateway) {
|
||||
$this->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<string, mixed> $form
|
||||
* @param callable(array<array<string, string>>): 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<string, mixed> $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<string, mixed> $form
|
||||
* @return HttpResponse
|
||||
*/
|
||||
public function submitFormReact(array $form): HttpResponse {
|
||||
return $this->submitForm($form, fn(array $results) => ViewHttpResponse::react('views/DisplayResults.tsx', $results));
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Http\HttpRequest;
|
||||
use App\Http\HttpResponse;
|
||||
use App\Http\ViewHttpResponse;
|
||||
use App\Model\TeamModel;
|
||||
use App\Validation\FieldValidationFail;
|
||||
use App\Validation\Validators;
|
||||
|
||||
class TeamController {
|
||||
private TeamModel $model;
|
||||
|
||||
/**
|
||||
* @param TeamModel $model
|
||||
*/
|
||||
public function __construct(TeamModel $model) {
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
public function displaySubmitTeam(): HttpResponse {
|
||||
return ViewHttpResponse::twig("insert_team.html.twig", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $request
|
||||
* @return HttpResponse
|
||||
*/
|
||||
public function submitTeam(array $request): HttpResponse {
|
||||
$errors = [];
|
||||
|
||||
$request = HttpRequest::from($request, $errors, [
|
||||
"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)) {
|
||||
$badFields = [];
|
||||
foreach ($errors as $e) {
|
||||
if ($e instanceof FieldValidationFail) {
|
||||
$badFields[] = $e->getFieldName();
|
||||
}
|
||||
}
|
||||
return ViewHttpResponse::twig('insert_team.html.twig', ['bad_fields' => $badFields]);
|
||||
}
|
||||
return $this->getTeam($this->model->createTeam($request['name'], $request['picture'], $request['mainColor'], $request['secondColor']));
|
||||
}
|
||||
|
||||
public function displayListTeamByName(): HttpResponse {
|
||||
return ViewHttpResponse::twig("list_team_by_name.html.twig", []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string , mixed> $request
|
||||
* @return HttpResponse
|
||||
*/
|
||||
public function listTeamByName(array $request): HttpResponse {
|
||||
$errors = [];
|
||||
$request = HttpRequest::from($request, $errors, [
|
||||
"name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()],
|
||||
]);
|
||||
|
||||
if (!empty($errors) && $errors[0] instanceof FieldValidationFail) {
|
||||
$badField = $errors[0]->getFieldName();
|
||||
return ViewHttpResponse::twig('list_team_by_name.html.twig', ['bad_field' => $badField]);
|
||||
}
|
||||
|
||||
$results = $this->model->listByName($request['name']);
|
||||
|
||||
if (empty($results)) {
|
||||
return ViewHttpResponse::twig('display_teams.html.twig', []);
|
||||
}
|
||||
|
||||
return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $results]);
|
||||
}
|
||||
|
||||
public function getTeam(int $id): HttpResponse {
|
||||
$result = $this->model->displayTeam($id);
|
||||
return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Http\HttpCodes;
|
||||
use App\Http\HttpResponse;
|
||||
use App\Http\JsonHttpResponse;
|
||||
use App\Http\ViewHttpResponse;
|
||||
use App\Model\TacticModel;
|
||||
|
||||
class VisualizerController {
|
||||
private TacticModel $tacticModel;
|
||||
|
||||
/**
|
||||
* @param TacticModel $tacticModel
|
||||
*/
|
||||
|
||||
public function __construct(TacticModel $tacticModel) {
|
||||
$this->tacticModel = $tacticModel;
|
||||
}
|
||||
|
||||
public function openVisualizer(int $id): HttpResponse {
|
||||
$tactic = $this->tacticModel->get($id);
|
||||
|
||||
if ($tactic == null) {
|
||||
return new JsonHttpResponse("la tactique " . $id . " n'existe pas", HttpCodes::NOT_FOUND);
|
||||
}
|
||||
|
||||
return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
|||||||
<?php
|
|||||||
|
|||||||
namespace IQBall\Core;
|
|||||||
|
|||||||
use IQBall\Core\Http\HttpResponse;
|
|||||||
|
|||||||
/**
|
|||||||
* Represent an action.
|
|||||||
* @template S session
|
|||||||
*/
|
|||||||
class Action {
|
|||||||
/**
|
|||||||
* @var callable(mixed[], S): HttpResponse $action action to call
|
|||||||
*/
|
|||||||
protected $action;
|
|||||||
|
|||||||
private bool $isAuthRequired;
|
|||||||
|
|||||||
/**
|
|||||||
* @param callable(mixed[], S): HttpResponse $action
|
|||||||
*/
|
|||||||
protected function __construct(callable $action, bool $isAuthRequired) {
|
|||||||
$this->action = $action;
|
|||||||
$this->isAuthRequired = $isAuthRequired;
|
|||||||
}
|
|||||||
|
|||||||
public function isAuthRequired(): bool {
|
|||||||
return $this->isAuthRequired;
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* Runs an action
|
|||||||
* @param mixed[] $params
|
|||||||
* @param S $session
|
|||||||
* @return HttpResponse
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
*/
|
|||||||
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<S> an action that does not require to have an authorization.
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
*/
|
|||||||
public static function noAuth(callable $action): Action {
|
|||||||
return new Action($action, false);
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* @param callable(mixed[], S): HttpResponse $action
|
|||||||
* @return Action<S> an action that does require to have an authorization.
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
*/
|
|||||||
public static function auth(callable $action): Action {
|
|||||||
return new Action($action, true);
|
|||||||
}
|
|||||||
}
|
@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace IQBall\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
class Connexion {
|
||||
class Connection {
|
||||
private PDO $pdo;
|
||||
|
||||
/**
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Data;
|
||||
|
||||
/**
|
||||
* Base class of a user account.
|
||||
* Contains the private information that we don't want
|
||||
* to share to other users, or non-needed public information
|
||||
*/
|
||||
class Account {
|
||||
/**
|
||||
* @var string $email account's mail address
|
||||
*/
|
||||
private string $email;
|
||||
|
||||
/**
|
||||
* @var string string token
|
||||
*/
|
||||
private string $token;
|
||||
|
||||
/**
|
||||
* @var string the account's username
|
||||
*/
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private int $id;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
* @param string $name
|
||||
* @param string $token
|
||||
* @param int $id
|
||||
*/
|
||||
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 {
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getToken(): string {
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Data;
|
||||
|
||||
class Team {
|
||||
private TeamInfo $info;
|
||||
|
||||
/**
|
||||
* @var Member[] maps users with their role
|
||||
*/
|
||||
private array $members;
|
||||
|
||||
/**
|
||||
* @param TeamInfo $info
|
||||
* @param Member[] $members
|
||||
*/
|
||||
public function __construct(TeamInfo $info, array $members = []) {
|
||||
$this->info = $info;
|
||||
$this->members = $members;
|
||||
}
|
||||
|
||||
public function getInfo(): TeamInfo {
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Member[]
|
||||
*/
|
||||
public function listMembers(): array {
|
||||
return $this->members;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
|||||||
<?php
|
|||||||
|
|||||||
namespace IQBall\Core\Gateway;
|
|||||||
|
|||||||
use IQBall\Core\Connection;
|
|||||||
use IQBall\Core\Data\Account;
|
|||||||
use PDO;
|
|||||||
|
|||||||
class AccountGateway {
|
|||||||
private Connection $con;
|
|||||||
|
|||||||
/**
|
|||||||
* @param Connection $con
|
|||||||
*/
|
|||||||
public function __construct(Connection $con) {
|
|||||||
$this->con = $con;
|
|||||||
}
|
|||||||
|
|||||||
|
|||||||
public function insertAccount(string $name, string $email, string $token, string $hash): int {
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
$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],
|
|||||||
]);
|
|||||||
return intval($this->con->lastInsertId());
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* @param string $email
|
|||||||
* @return array<string, mixed>|null
|
|||||||
*/
|
|||||||
private function getRowsFromMail(string $email): ?array {
|
|||||||
return $this->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) {
|
|||||||
return null;
|
|||||||
}
|
|||||||
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;
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* @param string $email
|
|||||||
* @return Account|null
|
|||||||
*/
|
|||||||
public function getAccountFromMail(string $email): ?Account {
|
|||||||
$acc = $this->getRowsFromMail($email);
|
|||||||
if (empty($acc)) {
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
return null;
|
|||||||
}
|
|||||||
|
|||||||
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)) {
|
|||||||
return null;
|
|||||||
}
|
|||||||
|
|||||||
return new Account($acc["email"], $acc["username"], $acc["token"], $acc["id"]);
|
|||||||
}
|
|||||||
|
|||||||
|
|||||||
}
|
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Gateway;
|
||||
|
||||
use IQBall\Core\Connection;
|
||||
use IQBall\Core\Data\Member;
|
||||
use IQBall\Core\Data\MemberRole;
|
||||
use PDO;
|
||||
|
||||
class MemberGateway {
|
||||
private Connection $con;
|
||||
|
||||
/**
|
||||
* @param Connection $con
|
||||
*/
|
||||
public function __construct(Connection $con) {
|
||||
$this->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],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Gateway;
|
||||
|
||||
use IQBall\Core\Connection;
|
||||
use IQBall\Core\Data\TacticInfo;
|
||||
use PDO;
|
||||
|
||||
class TacticInfoGateway {
|
||||
private Connection $con;
|
||||
|
||||
/**
|
||||
* @param Connection $con
|
||||
*/
|
||||
public function __construct(Connection $con) {
|
||||
$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",
|
||||
[":id" => [$id, PDO::PARAM_INT]]
|
||||
);
|
||||
|
||||
if (!isset($res[0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = $res[0];
|
||||
|
||||
return new TacticInfo($id, $row["name"], strtotime($row["creation_date"]), $row["owner"]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the nb last tactics created
|
||||
*
|
||||
* @param integer $nb
|
||||
* @return array<array<string,mixed>>
|
||||
*/
|
||||
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]]
|
||||
);
|
||||
if (count($res) == 0) {
|
||||
return null;
|
||||
}
|
||||
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)",
|
||||
[
|
||||
":name" => [$name, PDO::PARAM_STR],
|
||||
":owner" => [$owner, PDO::PARAM_INT],
|
||||
]
|
||||
);
|
||||
$row = $this->con->fetch(
|
||||
"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"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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",
|
||||
[
|
||||
":name" => [$name, PDO::PARAM_STR],
|
||||
":id" => [$id, PDO::PARAM_INT],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Gateway;
|
||||
|
||||
use IQBall\Core\Connection;
|
||||
use IQBall\Core\Data\Color;
|
||||
use IQBall\Core\Data\TeamInfo;
|
||||
use PDO;
|
||||
|
||||
class TeamGateway {
|
||||
private Connection $con;
|
||||
|
||||
public function __construct(Connection $con) {
|
||||
$this->con = $con;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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, main_color, second_color) VALUES (:team_name , :picture, :main_color, :second_color)",
|
||||
[
|
||||
":team_name" => [$name, PDO::PARAM_STR],
|
||||
":picture" => [$picture, PDO::PARAM_STR],
|
||||
":main_color" => [$mainColor, PDO::PARAM_STR],
|
||||
":second_color" => [$secondColor, PDO::PARAM_STR],
|
||||
]
|
||||
);
|
||||
return intval($this->con->lastInsertId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return TeamInfo[]
|
||||
*/
|
||||
public function listByName(string $name): array {
|
||||
$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 TeamInfo
|
||||
*/
|
||||
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 getTeamIdByName(string $name): ?int {
|
||||
return $this->con->fetch(
|
||||
"SELECT id FROM Team WHERE name = :name",
|
||||
[
|
||||
":name" => [$name, PDO::PARAM_INT],
|
||||
]
|
||||
)[0]['id'] ?? null;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,13 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
namespace IQBall\Core\Http;
|
||||
|
||||
/**
|
||||
* Utility class to define constants of used http codes
|
||||
*/
|
||||
class HttpCodes {
|
||||
public const OK = 200;
|
||||
public const FOUND = 302;
|
||||
public const BAD_REQUEST = 400;
|
||||
public const UNAUTHORIZED = 401;
|
||||
|
||||
public const FORBIDDEN = 403;
|
||||
|
||||
public const NOT_FOUND = 404;
|
||||
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
namespace IQBall\Core\Http;
|
||||
|
||||
use App\Validation\FieldValidationFail;
|
||||
use App\Validation\Validation;
|
||||
use App\Validation\ValidationFail;
|
||||
use App\Validation\Validator;
|
||||
use IQBall\Core\Validation\FieldValidationFail;
|
||||
use IQBall\Core\Validation\Validation;
|
||||
use IQBall\Core\Validation\ValidationFail;
|
||||
use IQBall\Core\Validation\Validator;
|
||||
use ArrayAccess;
|
||||
use Exception;
|
||||
|
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Http;
|
||||
|
||||
class HttpResponse {
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private array $headers;
|
||||
private int $code;
|
||||
|
||||
/**
|
||||
* @param int $code
|
||||
* @param array<string, string> $headers
|
||||
*/
|
||||
public function __construct(int $code, array $headers) {
|
||||
$this->code = $code;
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
public function getCode(): int {
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getHeaders(): array {
|
||||
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");
|
||||
}
|
||||
return new HttpResponse($code, ["Location" => $url]);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
|||||||
<?php
|
|||||||
|
|||||||
namespace IQBall\Core\Model;
|
|||||||
|
|||||||
use IQBall\Core\Data\Account;
|
|||||||
use IQBall\Core\Gateway\AccountGateway;
|
|||||||
use IQBall\Core\Validation\FieldValidationFail;
|
|||||||
use IQBall\Core\Validation\ValidationFail;
|
|||||||
|
|||||||
class AuthModel {
|
|||||||
private AccountGateway $gateway;
|
|||||||
|
|||||||
/**
|
|||||||
* @param AccountGateway $gateway
|
|||||||
*/
|
|||||||
public function __construct(AccountGateway $gateway) {
|
|||||||
$this->gateway = $gateway;
|
|||||||
}
|
|||||||
|
|||||||
|
|||||||
/**
|
|||||||
* @param string $username
|
|||||||
* @param string $password
|
|||||||
* @param string $confirmPassword
|
|||||||
* @param string $email
|
|||||||
* @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 &$failures): ?Account {
|
|||||||
maxime.batista marked this conversation as resolved
|
|||||||
|
|||||||
if ($password != $confirmPassword) {
|
|||||||
$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", "L'email existe déjà");
|
|||||||
}
|
|||||||
|
|||||||
if (!empty($failures)) {
|
|||||||
return null;
|
|||||||
}
|
|||||||
|
|||||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
|||||||
|
|||||||
$token = $this->generateToken();
|
|||||||
$accountId = $this->gateway->insertAccount($username, $email, $token, $hash);
|
|||||||
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));
|
|||||||
}
|
|||||||
|
|||||||
/**
|
|||||||
* @param string $email
|
|||||||
* @param string $password
|
|||||||
* @param ValidationFail[] $failures
|
|||||||
* @return Account|null the authenticated account or null if failures occurred
|
|||||||
*/
|
|||||||
public function login(string $email, string $password, array &$failures): ?Account {
|
|||||||
$hash = $this->gateway->getHash($email);
|
|||||||
if ($hash == null) {
|
|||||||
$failures[] = new FieldValidationFail("email", "l'addresse email n'est pas connue.");
|
|||||||
return null;
|
|||||||
}
|
|||||||
|
|||||||
if (!password_verify($password, $hash)) {
|
|||||||
$failures[] = new FieldValidationFail("password", "Mot de passe invalide.");
|
|||||||
return null;
|
|||||||
}
|
|||||||
|
|||||||
return $this->gateway->getAccountFromMail($email);
|
|||||||
}
|
|||||||
|
|||||||
|
|||||||
}
|
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Model;
|
||||
|
||||
use IQBall\Core\Gateway\TacticInfoGateway;
|
||||
use IQBall\Core\Validation\ValidationFail;
|
||||
use IQBall\Core\Data\TacticInfo;
|
||||
|
||||
class TacticModel {
|
||||
public const TACTIC_DEFAULT_NAME = "Nouvelle tactique";
|
||||
|
||||
|
||||
private TacticInfoGateway $tactics;
|
||||
|
||||
/**
|
||||
* @param TacticInfoGateway $tactics
|
||||
*/
|
||||
public function __construct(TacticInfoGateway $tactics) {
|
||||
$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->makeNew(self::TACTIC_DEFAULT_NAME, $ownerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to retrieve information about a tactic
|
||||
* @param int $id tactic identifier
|
||||
* @return TacticInfo|null or null if the identifier did not match a tactic
|
||||
*/
|
||||
public function get(int $id): ?TacticInfo {
|
||||
return $this->tactics->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the nb last tactics created
|
||||
*
|
||||
* @param integer $nb
|
||||
* @return array<array<string,mixed>>
|
||||
*/
|
||||
public function getLast(int $nb): ?array {
|
||||
return $this->tactics->getLast($nb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the name of a tactic
|
||||
* @param int $id the tactic identifier
|
||||
* @param string $name the new name to set
|
||||
* @return ValidationFail[] failures, if any
|
||||
*/
|
||||
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 [];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Model;
|
||||
|
||||
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 AccountGateway $users;
|
||||
private TeamGateway $teams;
|
||||
private MemberGateway $members;
|
||||
|
||||
/**
|
||||
* @param TeamGateway $gateway
|
||||
* @param MemberGateway $members
|
||||
* @param AccountGateway $users
|
||||
*/
|
||||
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 {
|
||||
return $this->teams->insert($name, $picture, $mainColor, $secondColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 TeamInfo[]
|
||||
*/
|
||||
public function listByName(string $name): array {
|
||||
return $this->teams->listByName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
$userId = $this->users->getAccountFromMail($mail)->getId();
|
||||
$this->members->remove($teamId, $userId);
|
||||
return $teamId;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Session;
|
||||
|
||||
use IQBall\Core\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;
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Session;
|
||||
|
||||
use IQBall\Core\Data\Account;
|
||||
|
||||
/**
|
||||
* A PHP session handle
|
||||
*/
|
||||
class PhpSessionHandle implements MutableSessionHandle {
|
||||
public static function init(): self {
|
||||
if (session_status() !== PHP_SESSION_NONE) {
|
||||
throw new \Exception("A php session is already started !");
|
||||
}
|
||||
session_start();
|
||||
return new PhpSessionHandle();
|
||||
}
|
||||
|
||||
public function getAccount(): ?Account {
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Session;
|
||||
|
||||
use IQBall\Core\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;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validation;
|
||||
namespace IQBall\Core\Validation;
|
||||
|
||||
class ComposedValidator extends Validator {
|
||||
private Validator $first;
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validation;
|
||||
namespace IQBall\Core\Validation;
|
||||
|
||||
/**
|
||||
* An error that concerns a field, with a bound message name
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validation;
|
||||
namespace IQBall\Core\Validation;
|
||||
|
||||
class FunctionValidator extends Validator {
|
||||
/**
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validation;
|
||||
namespace IQBall\Core\Validation;
|
||||
|
||||
/**
|
||||
* A simple validator that takes a predicate and an error factory
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validation;
|
||||
namespace IQBall\Core\Validation;
|
||||
|
||||
/**
|
||||
* Utility class for validation
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validation;
|
||||
namespace IQBall\Core\Validation;
|
||||
|
||||
abstract class Validator {
|
||||
/**
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace IQBall\Core\Validator;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
if ($tactic->getOwnerId() != $ownerId) {
|
||||
return ValidationFail::unauthorized("Vous ne pouvez pas accéder à cette tactique.");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
<?php
|
||||
|
||||
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
|
||||
* to share to other users, or non-needed public information
|
||||
*/
|
||||
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
|
||||
*/
|
||||
private AccountUser $user;
|
||||
|
||||
/**
|
||||
* @var Team[] account's teams
|
||||
*/
|
||||
private array $teams;
|
||||
|
||||
/**
|
||||
* @var int account's unique identifier
|
||||
*/
|
||||
private int $id;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
* @param string $phoneNumber
|
||||
* @param AccountUser $user
|
||||
* @param Team[] $teams
|
||||
* @param int $id
|
||||
*/
|
||||
public function __construct(string $email, string $phoneNumber, AccountUser $user, array $teams, int $id) {
|
||||
$this->email = $email;
|
||||
$this->phoneNumber = $phoneNumber;
|
||||
$this->user = $user;
|
||||
$this->teams = $teams;
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 getId(): int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Team[]
|
||||
*/
|
||||
public function getTeams(): array {
|
||||
return $this->teams;
|
||||
}
|
||||
|
||||
public function getUser(): AccountUser {
|
||||
return $this->user;
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use http\Url;
|
||||
|
||||
/**
|
||||
* This class implements the User and
|
||||
*/
|
||||
class AccountUser implements User {
|
||||
private string $name;
|
||||
private Url $profilePicture;
|
||||
private int $age;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param Url $profilePicture
|
||||
* @param int $age
|
||||
*/
|
||||
public function __construct(string $name, Url $profilePicture, int $age) {
|
||||
$this->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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use http\Url;
|
||||
|
||||
/**
|
||||
* Public information about a user
|
||||
*/
|
||||
interface User {
|
||||
/**
|
||||
* @return string the user's name
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* @return Url The user's profile picture image URL
|
||||
*/
|
||||
|
||||
public function getProfilePicture(): Url;
|
||||
|
||||
/**
|
||||
* @return int The user's age
|
||||
*/
|
||||
public function getAge(): int;
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Gateway;
|
||||
|
||||
use App\Connexion;
|
||||
use PDO;
|
||||
|
||||
class AuthGateway {
|
||||
private Connexion $con;
|
||||
|
||||
/**
|
||||
* @param Connexion $con
|
||||
*/
|
||||
public function __construct(Connexion $con) {
|
||||
$this->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<string,string>|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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Gateway;
|
||||
|
||||
use PDO;
|
||||
use App\Connexion;
|
||||
|
||||
/**
|
||||
* A sample gateway, that stores the sample form's result.
|
||||
*/
|
||||
class FormResultGateway {
|
||||
private Connexion $con;
|
||||
|
||||
public function __construct(Connexion $con) {
|
||||
$this->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<string, mixed>
|
||||
*/
|
||||
public function listResults(): array {
|
||||
return $this->con->fetch("SELECT * FROM FormEntries", []);
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Gateway;
|
||||
|
||||
use App\Connexion;
|
||||
use App\Data\TacticInfo;
|
||||
use PDO;
|
||||
|
||||
class TacticInfoGateway {
|
||||
private Connexion $con;
|
||||
|
||||
/**
|
||||
* @param Connexion $con
|
||||
*/
|
||||
public function __construct(Connexion $con) {
|
||||
$this->con = $con;
|
||||
}
|
||||
|
||||
public function get(int $id): ?TacticInfo {
|
||||
$res = $this->con->fetch(
|
||||
"SELECT * FROM TacticInfo WHERE id = :id",
|
||||
[":id" => [$id, PDO::PARAM_INT]]
|
||||
);
|
||||
|
||||
if (!isset($res[0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = $res[0];
|
||||
|
||||
return new TacticInfo($id, $row["name"], strtotime($row["creation_date"]));
|
||||
}
|
||||
|
||||
public function insert(string $name): TacticInfo {
|
||||
$this->con->exec(
|
||||
"INSERT INTO TacticInfo(name) VALUES(:name)",
|
||||
[":name" => [$name, PDO::PARAM_STR]]
|
||||
);
|
||||
$row = $this->con->fetch(
|
||||
"SELECT id, creation_date FROM TacticInfo WHERE :id = id",
|
||||
[':id' => [$this->con->lastInsertId(), PDO::PARAM_INT]]
|
||||
)[0];
|
||||
return new TacticInfo(intval($row["id"]), $name, strtotime($row["creation_date"]));
|
||||
}
|
||||
|
||||
public function updateName(int $id, string $name): void {
|
||||
$this->con->exec(
|
||||
"UPDATE TacticInfo SET name = :name WHERE id = :id",
|
||||
[
|
||||
":name" => [$name, PDO::PARAM_STR],
|
||||
":id" => [$id, PDO::PARAM_INT],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Gateway;
|
||||
|
||||
use App\Connexion;
|
||||
use PDO;
|
||||
|
||||
class TeamGateway {
|
||||
private Connexion $con;
|
||||
|
||||
public function __construct(Connexion $con) {
|
||||
$this->con = $con;
|
||||
}
|
||||
|
||||
public function insert(string $name, string $picture, string $mainColor, string $secondColor): void {
|
||||
$this->con->exec(
|
||||
"INSERT INTO Team(name, picture, mainColor, secondColor) VALUES (:teamName , :picture, :mainColor, :secondColor)",
|
||||
[
|
||||
":teamName" => [$name, PDO::PARAM_STR],
|
||||
":picture" => [$picture, PDO::PARAM_STR],
|
||||
":mainColor" => [$mainColor, PDO::PARAM_STR],
|
||||
":secondColor" => [$secondColor, PDO::PARAM_STR],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return array<string,mixed>[]
|
||||
*/
|
||||
public function listByName(string $name): array {
|
||||
return $this->con->fetch(
|
||||
"SELECT id,name,picture,mainColor,secondColor FROM Team WHERE name LIKE '%' || :name || '%'",
|
||||
[
|
||||
":name" => [$name, PDO::PARAM_STR],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return array<string,mixed>[]
|
||||
*/
|
||||
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],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return array<string,int>[]
|
||||
*/
|
||||
public function getIdTeamByName(string $name): array {
|
||||
return $this->con->fetch(
|
||||
"SELECT id FROM Team WHERE name = :name",
|
||||
[
|
||||
":name" => [$name, PDO::PARAM_STR],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return array<string,mixed>[]
|
||||
*/
|
||||
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],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
class HttpResponse {
|
||||
private int $code;
|
||||
|
||||
/**
|
||||
* @param int $code
|
||||
*/
|
||||
public function __construct(int $code) {
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
public function getCode(): int {
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public static function fromCode(int $code): HttpResponse {
|
||||
return new HttpResponse($code);
|
||||
}
|
||||
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use App\Controller\AuthController;
|
||||
use App\Gateway\AuthGateway;
|
||||
use App\Validation\FieldValidationFail;
|
||||
use App\Validation\ValidationFail;
|
||||
|
||||
class AuthModel {
|
||||
private AuthGateway $gateway;
|
||||
/**
|
||||
* @param AuthGateway $gateway
|
||||
*/
|
||||
public function __construct(AuthGateway $gateway) {
|
||||
$this->gateway = $gateway;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $confirmPassword
|
||||
* @param string $email
|
||||
* @return ValidationFail[]
|
||||
*/
|
||||
public function register(string $username, string $password, string $confirmPassword, string $email): array {
|
||||
$errors = [];
|
||||
|
||||
if ($password != $confirmPassword) {
|
||||
$errors[] = new FieldValidationFail("confirmpassword", "password and password confirmation are not equals");
|
||||
}
|
||||
|
||||
if ($this->gateway->mailExist($email)) {
|
||||
$errors[] = new FieldValidationFail("email", "email already exist");
|
||||
}
|
||||
|
||||
if(empty($errors)) {
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$this->gateway->insertAccount($username, $hash, $email);
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
* @return array<string,string>|null
|
||||
*/
|
||||
public function getUserFields(string $email): ?array {
|
||||
return $this->gateway->getUserFields($email);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
* @param string $password
|
||||
* @return ValidationFail[] $errors
|
||||
*/
|
||||
public function login(string $email, string $password): array {
|
||||
$errors = [];
|
||||
|
||||
if (!$this->gateway->mailExist($email)) {
|
||||
$errors[] = new FieldValidationFail("email", "email doesnt exists");
|
||||
return $errors;
|
||||
}
|
||||
$hash = $this->gateway->getUserHash($email);
|
||||
|
||||
if (!password_verify($password, $hash)) {
|
||||
$errors[] = new FieldValidationFail("password", "invalid password");
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use App\Data\TacticInfo;
|
||||
use App\Gateway\TacticInfoGateway;
|
||||
|
||||
class TacticModel {
|
||||
public const TACTIC_DEFAULT_NAME = "Nouvelle tactique";
|
||||
|
||||
|
||||
private TacticInfoGateway $tactics;
|
||||
|
||||
/**
|
||||
* @param TacticInfoGateway $tactics
|
||||
*/
|
||||
public function __construct(TacticInfoGateway $tactics) {
|
||||
$this->tactics = $tactics;
|
||||
}
|
||||
|
||||
public function makeNew(string $name): TacticInfo {
|
||||
return $this->tactics->insert($name);
|
||||
}
|
||||
|
||||
public function makeNewDefault(): ?TacticInfo {
|
||||
return $this->tactics->insert(self::TACTIC_DEFAULT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to retrieve information about a tactic
|
||||
* @param int $id tactic identifier
|
||||
* @return TacticInfo|null or null if the identifier did not match a tactic
|
||||
*/
|
||||
public function get(int $id): ?TacticInfo {
|
||||
return $this->tactics->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function updateName(int $id, string $name): bool {
|
||||
if ($this->tactics->get($id) == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->tactics->updateName($id, $name);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use App\Gateway\TeamGateway;
|
||||
use App\Data\Team;
|
||||
use App\Data\Member;
|
||||
use App\Data\MemberRole;
|
||||
use App\Data\Color;
|
||||
|
||||
class TeamModel {
|
||||
private TeamGateway $gateway;
|
||||
|
||||
/**
|
||||
* @param TeamGateway $gateway
|
||||
*/
|
||||
public function __construct(TeamGateway $gateway) {
|
||||
$this->gateway = $gateway;
|
||||
}
|
||||
|
||||
public function createTeam(string $name, string $picture, string $mainColor, string $secondColor): int {
|
||||
$this->gateway->insert($name, $picture, $mainColor, $secondColor);
|
||||
$result = $this->gateway->getIdTeamByName($name);
|
||||
return intval($result[0]['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Team[]
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
public function displayTeam(int $id): Team {
|
||||
$members = [];
|
||||
$result = $this->gateway->getTeamById($id)[0];
|
||||
$resultMembers = $this->gateway->getMembersById($id);
|
||||
foreach ($resultMembers as $row) {
|
||||
if ($row['role'] == 'C') {
|
||||
$role = MemberRole::coach();
|
||||
} else {
|
||||
$role = MemberRole::player();
|
||||
}
|
||||
$members[] = new Member($row['id'], $role);
|
||||
}
|
||||
return new Team(intval($result['id']), $result['name'], $result['picture'], Color::from($result['mainColor']), Color::from($result['secondColor']), $members);
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Twig view</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Hello, this is a sample form made in Twig !</h1>
|
||||
|
||||
<form action="submit-twig" method="POST">
|
||||
<label for="name">your name: </label>
|
||||
<input type="text" id="name" name="name"/>
|
||||
<label for="password">a little description about yourself: </label>
|
||||
<input type="text" id="password" name="description"/>
|
||||
<input type="submit" value="click me to submit!"/>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in new issue
needs some documentation here plz