Merge branch 'salva' of https://codefirst.iut.uca.fr/git/IQBall/Application-Web into salva
continuous-integration/drone/push Build is passing Details

pull/22/head
samuel 1 year ago
commit 49cdcb8972

@ -1,7 +1,7 @@
{
"autoload": {
"psr-4": {
"App\\": "src/"
"IQBall\\": "src/"
}
},
"require": {

@ -3,13 +3,10 @@ parameters:
level: 6
paths:
- src
- public
scanFiles:
- config.php
- sql/database.php
- profiles/dev-config-profile.php
- profiles/prod-config-profile.php
- public/api/index.php
excludePaths:
- src/react-display-file.php
- public/api/index.php
- src/App/react-display-file.php

@ -5,144 +5,33 @@ require "../../vendor/autoload.php";
require "../../sql/database.php";
require "../utils.php";
use App\Connexion;
use App\Controller\Api\APIAuthController;
use App\Controller\Api\APITacticController;
use App\Data\Account;
use App\Gateway\AccountGateway;
use App\Gateway\TacticInfoGateway;
use App\Http\HttpResponse;
use App\Http\JsonHttpResponse;
use App\Http\ViewHttpResponse;
use App\Model\AuthModel;
use App\Model\TacticModel;
use App\Session\PhpSessionHandle;
use App\Validation\ValidationFail;
use IQBall\Api\API;
use IQBall\Api\Controller\APIAuthController;
use IQBall\Api\Controller\APITacticController;
use IQBall\Core\Action;
use IQBall\Core\Connection;
use IQBall\Core\Data\Account;
use IQBall\Core\Gateway\AccountGateway;
use IQBall\Core\Gateway\TacticInfoGateway;
use IQBall\Core\Model\AuthModel;
use IQBall\Core\Model\TacticModel;
function getTacticController(): APITacticController {
return new APITacticController(new TacticModel(new TacticInfoGateway(new Connexion(get_database()))));
return new APITacticController(new TacticModel(new TacticInfoGateway(new Connection(get_database()))));
}
function getAuthController(): APIAuthController {
return new APIAuthController(new AuthModel(new AccountGateway(new Connexion(get_database()))));
return new APIAuthController(new AuthModel(new AccountGateway(new Connection(get_database()))));
}
/**
* A Front controller action
*/
//TODO workaround for generic Action
class ApiAction {
/**
* @var callable(mixed[]): HttpResponse $action action to call
*/
private $action;
function getRoutes(): AltoRouter {
$router = new AltoRouter();
$router->setBasePath(get_public_path() . "/api");
private bool $isAuthRequired;
$router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc)));
$router->map("POST", "/auth", Action::noAuth(fn() => getAuthController()->authorize()));
/**
* @param callable(mixed[]): HttpResponse $action
*/
private function __construct(callable $action, bool $isAuthRequired) {
$this->action = $action;
$this->isAuthRequired = $isAuthRequired;
}
public function isAuthRequired(): bool {
return $this->isAuthRequired;
}
/**
* @param mixed[] $params
* @param ?Account $account
* @return HttpResponse
*/
public function run(array $params, ?Account $account): HttpResponse {
$params = array_values($params);
if ($this->isAuthRequired) {
if ($account == null) {
throw new Exception("action requires authorization.");
}
$params[] = $account;
}
return call_user_func_array($this->action, $params);
}
/**
* @param callable(mixed[]): HttpResponse $action
* @return ApiAction an action that does not require to have an authorization.
*/
public static function noAuth(callable $action): ApiAction {
return new ApiAction($action, false);
}
/**
* @param callable(mixed[]): HttpResponse $action
* @return ApiAction an action that does require to have an authorization.
*/
public static function auth(callable $action): ApiAction {
return new ApiAction($action, true);
}
}
/**
* @param mixed[] $match
* @return HttpResponse
* @throws Exception
*/
function handleMatch(array $match): HttpResponse {
if (!$match) {
return new JsonHttpResponse([ValidationFail::notFound("not found")]);
}
$action = $match['target'];
if (!$action instanceof ApiAction) {
throw new Exception("routed action is not an Action object.");
}
$auth = null;
if ($action->isAuthRequired()) {
$auth = tryGetAuthorization();
if ($auth == null) {
return new JsonHttpResponse([ValidationFail::unauthorized("Missing or invalid 'Authorization' header.")]);
}
}
return $action->run($match['params'], $auth);
return $router;
}
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 Connexion(get_database()));
return $gateway->getAccountFromToken($token);
}
$router = new AltoRouter();
$router->setBasePath(get_public_path() . "/api");
$router->map("POST", "/tactic/[i:id]/edit/name", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc)));
$router->map("GET", "/tactic/[i:id]", ApiAction::auth(fn(int $id, Account $acc) => getTacticController()->getTacticInfo($id, $acc)));
$router->map("POST", "/tactic/new", ApiAction::auth(fn(Account $acc) => getTacticController()->newTactic($acc)));
$router->map("POST", "/auth", ApiAction::noAuth(fn() => getAuthController()->authorize()));
$match = $router->match();
$response = handleMatch($match);
http_response_code($response->getCode());
if ($response instanceof JsonHttpResponse) {
header('Content-type: application/json');
echo $response->getJson();
} elseif ($response instanceof ViewHttpResponse) {
throw new Exception("API returned a view http response.");
}
Api::render(API::handleMatch(getRoutes()->match()));

@ -4,96 +4,88 @@
require "../vendor/autoload.php";
require "../config.php";
require "../sql/database.php";
require "utils.php";
require "../src/react-display.php";
use App\Controller\AuthController;
use App\Controller\EditorController;
use App\Controller\Route\Action;
use App\Controller\Route\FrontController;
use App\Controller\TeamController;
use App\Controller\UserController;
use App\Controller\VisualizerController;
use App\Gateway\AccountGateway;
use App\Gateway\TacticInfoGateway;
use App\Gateway\TeamGateway;
use App\Model\AuthModel;
use App\Model\TacticModel;
use App\Model\TeamModel;
use App\Session\MutableSessionHandle;
use App\Session\PhpSessionHandle;
use App\Connexion;
use App\Session\SessionHandle;
$connexion = new Connexion(get_database());
require "../src/utils.php";
require "../src/App/react-display.php";
use IQBall\App\App;
use IQBall\App\Controller\AuthController;
use IQBall\App\Controller\EditorController;
use IQBall\App\Controller\TeamController;
use IQBall\App\Controller\UserController;
use IQBall\App\Controller\VisualizerController;
use IQBall\Core\Action;
use IQBall\Core\Connection;
use IQBall\Core\Gateway\AccountGateway;
use IQBall\Core\Gateway\TacticInfoGateway;
use IQBall\Core\Gateway\TeamGateway;
use IQBall\Core\Model\AuthModel;
use IQBall\Core\Model\TacticModel;
use IQBall\Core\Model\TeamModel;
use IQBall\Core\Session\PhpSessionHandle;
use IQBall\Core\Session\SessionHandle;
function getConnection(): Connection {
return new Connection(get_database());
}
function getUserController(): UserController {
global $connexion;
return new UserController(new TacticModel(new TacticInfoGateway($connexion)));
return new UserController(new TacticModel(new TacticInfoGateway(getConnection())));
}
function getVisualizerController(): VisualizerController {
global $connexion;
return new VisualizerController(new TacticModel(new TacticInfoGateway($connexion)));
return new VisualizerController(new TacticModel(new TacticInfoGateway(getConnection())));
}
function getEditorController(): EditorController {
global $connexion;
return new EditorController(new TacticModel(new TacticInfoGateway($connexion)));
return new EditorController(new TacticModel(new TacticInfoGateway(getConnection())));
}
function getTeamController(): TeamController {
global $connexion;
return new TeamController(new TeamModel(new TeamGateway($connexion)));
return new TeamController(new TeamModel(new TeamGateway(getConnection())));
}
function getAuthController(): AuthController {
global $connexion;
return new AuthController(new AuthModel(new AccountGateway($connexion)));
return new AuthController(new AuthModel(new AccountGateway(getConnection())));
}
function initFrontController(FrontController $fc) {
function getRoutes(): AltoRouter {
global $basePath;
$ar = new AltoRouter();
$ar->setBasePath($basePath);
//authentication
$fc->addRoute("GET", "/login", Action::noAuth(fn() => getAuthController()->displayLogin()));
$fc->addRoute("GET", "/register", Action::noAuth(fn() => getAuthController()->displayRegister()));
$fc->addRoute("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s)));
$fc->addRoute("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s)));
$ar->map("GET", "/login", Action::noAuth(fn() => getAuthController()->displayLogin()));
$ar->map("GET", "/register", Action::noAuth(fn() => getAuthController()->displayRegister()));
$ar->map("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s)));
$ar->map("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s)));
//user-related
$fc->addRoute("GET", "/home", Action::auth(fn(SessionHandle $s) => getUserController()->home($s)));
$fc->addRoute("GET", "/settings", Action::auth(fn(SessionHandle $s) => getUserController()->settings($s)));
$ar->map("GET", "/home", 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
$fc->addRoute("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s)));
$fc->addRoute("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s)));
$fc->addRoute("GET", "/tactic/new", Action::auth(fn(SessionHandle $s) => getEditorController()->createNew($s)));
$ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s)));
$ar->map("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s)));
$ar->map("GET", "/tactic/new", Action::auth(fn(SessionHandle $s) => getEditorController()->createNew($s)));
//team-related
$fc->addRoute("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s)));
$fc->addRoute("POST", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->submitTeam($_POST, $s)));
$fc->addRoute("GET", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->displayListTeamByName($s)));
$fc->addRoute("POST", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->listTeamByName($_POST, $s)));
$fc->addRoute("GET", "/team/[i:id]", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->displayTeam($id, $s)));
$fc->addRoute("GET", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->displayAddMember($s)));
$fc->addRoute("POST", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->addMember($_POST, $s)));
$fc->addRoute("GET", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->displayDeleteMember($s)));
$fc->addRoute("POST", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->deleteMember($_POST, $s)));
$ar->map("GET", "/team/new", 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;
}
//this is a global variable
$basePath = get_public_path();
function run() {
global $basePath;
$fc = new FrontController($basePath);
initFrontController($fc);
$fc->run(PhpSessionHandle::init());
}
run();
App::render(App::runMatch(getRoutes()->match(), PhpSessionHandle::init()));

@ -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);
}
}

@ -1,13 +1,13 @@
<?php
namespace App\Controller\Api;
use App\Controller\Control;
use App\Http\HttpRequest;
use App\Http\HttpResponse;
use App\Http\JsonHttpResponse;
use App\Model\AuthModel;
use App\Validation\Validators;
namespace IQBall\Api\Controller;
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;

@ -0,0 +1,42 @@
<?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;
}
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,90 @@
<?php
namespace IQBall\App;
use IQBall\Core\Action;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Session\MutableSessionHandle;
use IQBall\Core\Validation\ValidationFail;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use Twig\Loader\FilesystemLoader;
class App {
public static function render(HttpResponse $response): void {
http_response_code($response->getCode());
foreach ($response->getHeaders() as $header => $value) {
header("$header: $value");
}
if ($response instanceof ViewHttpResponse) {
self::renderView($response);
} elseif ($response instanceof JsonHttpResponse) {
header('Content-type: application/json');
echo $response->getJson();
}
}
private static function renderView(ViewHttpResponse $response): void {
$file = $response->getFile();
$args = $response->getArguments();
switch ($response->getViewKind()) {
case ViewHttpResponse::REACT_VIEW:
send_react_front($file, $args);
break;
case ViewHttpResponse::TWIG_VIEW:
try {
$loader = new FilesystemLoader('../src/App/Views/');
$twig = new Environment($loader);
$twig->display($file, $args);
} catch (RuntimeError | SyntaxError | LoaderError $e) {
http_response_code(500);
echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s");
throw $e;
}
break;
}
}
/**
* @param Action<MutableSessionHandle> $action
* @param mixed[] $params
* @param MutableSessionHandle $session
* @return HttpResponse
*/
private static function runAction(Action $action, array $params, MutableSessionHandle $session): HttpResponse {
global $basePath;
if ($action->isAuthRequired()) {
$account = $session->getAccount();
if ($account == null) {
// put in the session the initial url the user wanted to get
$session->setInitialTarget($_SERVER['REQUEST_URI']);
return HttpResponse::redirect($basePath . "/login");
}
}
return $action->run($params, $session);
}
/**
* @param array<string, mixed>|false $match
* @param MutableSessionHandle $session
* @return HttpResponse
*/
public static function runMatch($match, MutableSessionHandle $session): HttpResponse {
if (!$match) {
return ViewHttpResponse::twig("error.html.twig", [
'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")],
], HttpCodes::NOT_FOUND);
}
return self::runAction($match['target'], $match['params'], $session);
}
}

@ -1,14 +1,14 @@
<?php
namespace App\Controller;
namespace IQBall\Core\Route;
use App\Http\HttpCodes;
use App\Http\HttpRequest;
use App\Http\HttpResponse;
use App\Http\JsonHttpResponse;
use App\Http\ViewHttpResponse;
use App\Validation\ValidationFail;
use App\Validation\Validator;
use IQBall\App\ViewHttpResponse;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Validation\ValidationFail;
use IQBall\Core\Validation\Validator;
class Control {
/**
@ -57,6 +57,4 @@ class Control {
return call_user_func_array($run, [$request]);
}
}

@ -1,14 +1,14 @@
<?php
namespace App\Controller;
namespace IQBall\App\Controller;
use App\Http\HttpRequest;
use App\Http\HttpResponse;
use App\Http\ViewHttpResponse;
use App\Model\AuthModel;
use App\Session\MutableSessionHandle;
use App\Validation\ValidationFail;
use App\Validation\Validators;
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;
@ -29,7 +29,7 @@ class AuthController {
* @param ValidationFail[] $fails
* @return HttpResponse
*/
private function displayBadFields(string $viewName, array $fails): HttpResponse{
private function displayBadFields(string $viewName, array $fails): HttpResponse {
return ViewHttpResponse::twig($viewName, ['fails' => $fails]);
}
@ -44,7 +44,7 @@ class AuthController {
"username" => [Validators::name(), Validators::lenBetween(2, 32)],
"password" => [Validators::lenBetween(6, 256)],
"confirmpassword" => [Validators::lenBetween(6, 256)],
"email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/","invalide"),Validators::lenBetween(5, 256)],
"email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/", "invalide"),Validators::lenBetween(5, 256)],
]);
if (!empty($fails)) {
return $this->displayBadFields("display_register.html.twig", $fails);
@ -73,7 +73,7 @@ class AuthController {
$fails = [];
$request = HttpRequest::from($request, $fails, [
"password" => [Validators::lenBetween(6, 256)],
"email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/","invalide"),Validators::lenBetween(5, 256)],
"email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/", "invalide"),Validators::lenBetween(5, 256)],
]);
if (!empty($fails)) {
return $this->displayBadFields("display_login.html.twig", $fails);

@ -1,14 +1,14 @@
<?php
namespace App\Controller;
use App\Data\TacticInfo;
use App\Http\HttpCodes;
use App\Http\HttpResponse;
use App\Http\ViewHttpResponse;
use App\Model\TacticModel;
use App\Session\SessionHandle;
use App\Validator\TacticValidator;
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;

@ -1,14 +1,14 @@
<?php
namespace App\Controller;
namespace IQBall\App\Controller;
use App\Http\HttpRequest;
use App\Http\HttpResponse;
use App\Http\ViewHttpResponse;
use App\Model\TeamModel;
use App\Session\SessionHandle;
use App\Validation\FieldValidationFail;
use App\Validation\Validators;
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;
@ -33,19 +33,22 @@ class TeamController {
return ViewHttpResponse::twig("delete_member.html.twig", []);
}
/**
* @param array<string, mixed> $request
* @param SessionHandle $session
* @return HttpResponse
*/
public function submitTeam(array $request, SessionHandle $session): HttpResponse {
$errors = [];
$request = HttpRequest::from($request, $errors, [
$failures = [];
$request = HttpRequest::from($request, $failures, [
"name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()],
"mainColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')],
"secondColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')],
"picture" => [Validators::isURL()],
]);
if (!empty($errors)) {
if (!empty($failures)) {
$badFields = [];
foreach ($errors as $e) {
foreach ($failures as $e) {
if ($e instanceof FieldValidationFail) {
$badFields[] = $e->getFieldName();
}
@ -88,23 +91,33 @@ class TeamController {
return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]);
}
/**
* @param array<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()],
"mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)]
"mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)],
]);
return $this->displayTeam($this->model->addMember($request['mail'], intval($request['team']), $request['role']), $session);
}
/**
* @param array<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()],
"mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)]
"mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)],
]);
return $this->displayTeam($this->model->deleteMember($request['mail'], intval($request['team'])), $session);

@ -1,16 +1,13 @@
<?php
namespace App\Controller;
namespace IQBall\App\Controller;
use App\Connexion;
use App\Gateway\TacticInfoGateway;
use App\Http\HttpResponse;
use App\Http\ViewHttpResponse;
use App\Model\TacticModel;
use App\Session\SessionHandle;
use IQBall\Core\Http\HttpResponse;
use IQBall\App\ViewHttpResponse;
use IQBall\Core\Model\TacticModel;
use IQBall\Core\Session\SessionHandle;
class UserController {
private TacticModel $tactics;
/**
@ -37,4 +34,4 @@ class UserController {
return ViewHttpResponse::twig("account_settings.twig", []);
}
}
}

@ -1,13 +1,13 @@
<?php
namespace App\Controller;
use App\Http\HttpCodes;
use App\Http\HttpResponse;
use App\Http\ViewHttpResponse;
use App\Model\TacticModel;
use App\Session\SessionHandle;
use App\Validator\TacticValidator;
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;

@ -1,6 +1,9 @@
<?php
namespace App\Http;
namespace IQBall\App;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\HttpResponse;
class ViewHttpResponse extends HttpResponse {
public const TWIG_VIEW = 0;

@ -54,7 +54,7 @@
<h1>IQ Ball</h1>
<div id="account" onclick="location.pathname='/settings'">
<img
src="front/assets/icon/account.svg"
src="../../../front/assets/icon/account.svg"
alt="Account logo"
/>
<p>Mon profil<p>

@ -1,63 +0,0 @@
<?php
namespace App\Controller\Api;
use App\Controller\Control;
use App\Data\Account;
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, 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);
}
public function newTactic(Account $account): HttpResponse {
return Control::runChecked([
"name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()],
], function (HttpRequest $request) use ($account) {
$tactic = $this->model->makeNew($request["name"], $account->getId());
$id = $tactic->getId();
return new JsonHttpResponse(["id" => $id]);
}, true);
}
public function getTacticInfo(int $id, Account $account): HttpResponse {
$tactic_info = $this->model->get($id);
if ($tactic_info == null) {
return new JsonHttpResponse("could not find tactic #$id", HttpCodes::NOT_FOUND);
}
return new JsonHttpResponse($tactic_info);
}
}

@ -1,71 +0,0 @@
<?php
namespace App\Controller\Route;
use App\Http\HttpResponse;
use App\Session\SessionHandle;
use Exception;
/**
* A Front controller action
*/
class Action {
/**
* @var callable(mixed[]): HttpResponse $action action to call
*/
private $action;
private bool $isAuthRequired;
/**
* @param callable(mixed[]): HttpResponse $action
*/
private function __construct(callable $action, bool $isAuthRequired) {
$this->action = $action;
$this->isAuthRequired = $isAuthRequired;
}
public function isAuthRequired(): bool {
return $this->isAuthRequired;
}
/**
* @param mixed[] $params
* @param SessionHandle $session
* @return HttpResponse
* @throws Exception <p>
* thrown if this action is required to be authenticated, but the given session does not contain a logged-in account.
* </p>
* <p>
* Caller is supposed to ensure that the user is logged-in before, if `$this->isAuthRequired()` is true before
* running this action.
* </p>
*/
public function run(array $params, SessionHandle $session): HttpResponse {
$params = array_values($params);
if ($this->isAuthRequired) {
if ($session->getAccount() == null) {
throw new Exception("action requires authorization.");
}
}
$params[] = $session;
return call_user_func_array($this->action, $params);
}
/**
* @param callable(mixed[]): HttpResponse $action
* @return Action an action that does not require to have an authorization.
*/
public static function noAuth(callable $action): Action {
return new Action($action, false);
}
/**
* @param callable(mixed[]): HttpResponse $action
* @return Action an action that does require to have an authorization.
*/
public static function auth(callable $action): Action {
return new Action($action, true);
}
}

@ -1,143 +0,0 @@
<?php
namespace App\Controller\Route;
use AltoRouter;
use App\Http\HttpCodes;
use App\Http\HttpResponse;
use App\Http\JsonHttpResponse;
use App\Http\ViewHttpResponse;
use App\Session\MutableSessionHandle;
use App\Validation\ValidationFail;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use Twig\Loader\FilesystemLoader;
class FrontController {
private AltoRouter $router;
private string $basePath;
public function __construct(string $basePath) {
$this->router = $this->createRouter($basePath);
$this->basePath = $basePath;
}
public function addRoute(string $method, string $path, Action $action): void {
$this->router->map($method, $path, $action);
}
/**
* @param MutableSessionHandle $session
* @return void
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*/
public function run(MutableSessionHandle $session): void {
$match = $this->router->match();
if ($match) {
$this->handleMatch($match, $session);
return;
}
$this->displayViewByKind(ViewHttpResponse::twig("error.html.twig", [
'failures' => [ValidationFail::notFound("Could not find page ${_SERVER['REQUEST_URI']}.")],
], HttpCodes::NOT_FOUND));
}
/**
* Create a new instance of an AltoRouter
*
* @param string $basePath
* @return AltoRouter
*/
public function createRouter(string $basePath): AltoRouter {
$router = new AltoRouter();
$router->setBasePath($basePath);
return $router;
}
/**
* @param array<string, mixed> $match
* @param MutableSessionHandle $session
* @return void
*/
private function handleMatch(array $match, MutableSessionHandle $session): void {
$action = $match['target'];
$params = array_values($match["params"]);
$this->handleResponseByType($this->tryToCall($action, $params, $session));
}
/**
* @param Action $action
* @param array<int, mixed> $params
* @param MutableSessionHandle $session
* @return HttpResponse
*/
private function tryToCall(Action $action, array $params, MutableSessionHandle $session): HttpResponse {
$account = null;
if ($action->isAuthRequired()) {
$account = $session->getAccount();
if ($account == null) {
// put in the session the initial url the user wanted to get
$session->setInitialTarget($_SERVER['REQUEST_URI']);
return HttpResponse::redirect($this->basePath . "/login");
}
}
return $action->run($params, $session);
}
/**
* Redirect the return by the response's type
*
* @param HttpResponse $response
* @return void
*/
private function handleResponseByType(HttpResponse $response): void {
http_response_code($response->getCode());
foreach ($response->getHeaders() as $header => $value) {
header("$header: $value");
}
if ($response instanceof ViewHttpResponse) {
$this->displayViewByKind($response);
} elseif ($response instanceof JsonHttpResponse) {
header('Content-type: application/json');
echo $response->getJson();
}
}
/**
* Use the right method to display the response
*
* @param ViewHttpResponse $response
* @return void
*/
private function displayViewByKind(ViewHttpResponse $response): void {
$file = $response->getFile();
$args = $response->getArguments();
switch ($response->getViewKind()) {
case ViewHttpResponse::REACT_VIEW:
send_react_front($file, $args);
break;
case ViewHttpResponse::TWIG_VIEW:
try {
$loader = new FilesystemLoader('../src/Views/');
$twig = new Environment($loader);
$twig->display($file, $args);
} catch (RuntimeError|SyntaxError|LoaderError $e) {
http_response_code(500);
echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s");
throw $e;
}
break;
}
}
}

@ -0,0 +1,56 @@
<?php
namespace IQBall\Core;
use IQBall\Core\Http\HttpResponse;
/**
* @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;
}
/**
* @param mixed[] $params
* @param S $session
* @return HttpResponse
*/
public function run(array $params, $session): HttpResponse {
$params = array_values($params);
$params[] = $session;
return call_user_func_array($this->action, $params);
}
/**
* @param callable(mixed[], S): HttpResponse $action
* @return Action<S> an action that does not require to have an authorization.
*/
public static function noAuth(callable $action): Action {
return new Action($action, false);
}
/**
* @param callable(mixed[], S): HttpResponse $action
* @return Action<S> an action that does require to have an authorization.
*/
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;
/**

@ -1,6 +1,6 @@
<?php
namespace App\Data;
namespace IQBall\Core\Data;
/**
* Base class of a user account.

@ -1,6 +1,6 @@
<?php
namespace App\Data;
namespace IQBall\Core\Data;
use http\Url;

@ -1,6 +1,6 @@
<?php
namespace App\Data;
namespace IQBall\Core\Data;
use InvalidArgumentException;

@ -1,13 +1,13 @@
<?php
namespace App\Data;
namespace IQBall\Core\Data;
/**
* information about a team member
*/
class Member {
/**
* @var AccountUser The member's user account
* @var int The member's user account
*/
private int $userId;
@ -49,8 +49,7 @@ class Member {
/**
* @return int
*/
public function getTeamId(): int
{
public function getTeamId(): int {
return $this->teamId;
}
}

@ -1,8 +1,8 @@
<?php
namespace App\Data;
namespace IQBall\Core\Data;
use http\Exception\InvalidArgumentException;
use InvalidArgumentException;
/**
* Enumeration class workaround

@ -1,6 +1,6 @@
<?php
namespace App\Data;
namespace IQBall\Core\Data;
class TacticInfo implements \JsonSerializable {
private int $id;

@ -1,6 +1,6 @@
<?php
namespace App\Data;
namespace IQBall\Core\Data;
class Team {
private int $id;
@ -72,7 +72,7 @@ class Team {
return $this->members;
}
public function addMember(Member $m) {
public function addMember(Member $m): void {
$this->members[] = $m;
}

@ -1,6 +1,6 @@
<?php
namespace App\Data;
namespace IQBall\Core\Data;
use http\Url;

@ -1,18 +1,18 @@
<?php
namespace App\Gateway;
namespace IQBall\Core\Gateway;
use App\Connexion;
use App\Data\Account;
use IQBall\Core\Connection;
use IQBall\Core\Data\Account;
use PDO;
class AccountGateway {
private Connexion $con;
private Connection $con;
/**
* @param Connexion $con
* @param Connection $con
*/
public function __construct(Connexion $con) {
public function __construct(Connection $con) {
$this->con = $con;
}

@ -1,17 +1,17 @@
<?php
namespace App\Gateway;
namespace IQBall\Core\Gateway;
use App\Connexion;
use IQBall\Core\Connection;
use PDO;
class AuthGateway {
private Connexion $con;
private Connection $con;
/**
* @param Connexion $con
* @param Connection $con
*/
public function __construct(Connexion $con) {
public function __construct(Connection $con) {
$this->con = $con;
}

@ -1,18 +1,18 @@
<?php
namespace App\Gateway;
namespace IQBall\Core\Gateway;
use App\Connexion;
use App\Data\TacticInfo;
use IQBall\Core\Connection;
use IQBall\Core\Data\TacticInfo;
use PDO;
class TacticInfoGateway {
private Connexion $con;
private Connection $con;
/**
* @param Connexion $con
* @param Connection $con
*/
public function __construct(Connexion $con) {
public function __construct(Connection $con) {
$this->con = $con;
}
@ -38,7 +38,7 @@ class TacticInfoGateway {
* @param integer $nb
* @return array<array<string,mixed>>
*/
public function getLast(int $nb) : ?array {
public function getLast(int $nb): ?array {
$res = $this->con->fetch(
"SELECT * FROM Tactic ORDER BY creation_date DESC LIMIT :nb ",
[":nb" => [$nb, PDO::PARAM_INT]]

@ -1,14 +1,14 @@
<?php
namespace App\Gateway;
namespace IQBall\Core\Gateway;
use App\Connexion;
use IQBall\Core\Connection;
use PDO;
class TeamGateway {
private Connexion $con;
private Connection $con;
public function __construct(Connexion $con) {
public function __construct(Connection $con) {
$this->con = $con;
}
@ -25,18 +25,22 @@ class TeamGateway {
}
public function insertMember(int $idTeam, int $idMember, string $role) {
public function insertMember(int $idTeam, int $idMember, string $role): void {
$this->con->exec(
"INSERT INTO Member(idTeam, idMember, role) VALUES (:idTeam , :idMember, :role)",
[
":idTeam" => [$idTeam, PDO::PARAM_INT],
":idMember" => [$idMember, PDO::PARAM_INT],
":role" => [$role, PDO::PARAM_STR]
":role" => [$role, PDO::PARAM_STR],
]
);
}
/**
* @param string $name
* @return array<string, mixed>[]
*/
public function listByName(string $name): array {
return $this->con->fetch(
"SELECT id,name,picture,mainColor,secondColor FROM Team WHERE name LIKE '%' || :name || '%'",
@ -48,28 +52,28 @@ class TeamGateway {
/**
* @param int $id
* @return array<string,mixed>[]
* @return array<string,mixed>
*/
public function getTeamById(int $id): array {
public function getTeamById(int $id): ?array {
return $this->con->fetch(
"SELECT id,name,picture,mainColor,secondColor FROM Team WHERE id = :id",
[
":id" => [$id, PDO::PARAM_INT],
]
);
)[0] ?? null;
}
/**
* @param string $name
* @return array<string,int>[]
* @return int|null
*/
public function getIdTeamByName(string $name): array {
public function getIdTeamByName(string $name): ?int {
return $this->con->fetch(
"SELECT id FROM Team WHERE name = :name",
[
":name" => [$name, PDO::PARAM_STR],
":name" => [$name, PDO::PARAM_INT],
]
);
)[0]['id'] ?? null;
}
/**
@ -78,23 +82,27 @@ class TeamGateway {
*/
public function getMembersById(int $id): array {
return $this->con->fetch(
"SELECT a.id,m.role,a.email,a.username FROM Account a,Team t,Member m WHERE t.id = :id AND m.idTeam = t.id AND m.idMember = a.id",
[
":id" => [$id, PDO::PARAM_INT]
"SELECT a.id,m.role,a.email,a.username FROM Account a,Team t,Member m WHERE t.id = :id AND m.idTeam = t.id AND m.idMember = a.id",
[
":id" => [$id, PDO::PARAM_INT],
]
);
}
public function getMemberIdByMail($mail) : array {
/**
* @param string $mail
* @return int|null
*/
public function getMemberIdByMail(string $mail): ?int {
return $this->con->fetch(
"SELECT id FROM Account WHERE email = :mail",
[
":mail" => [$mail, PDO::PARAM_STR]
":mail" => [$mail, PDO::PARAM_STR],
]
);
)[0]['id'] ?? null;
}
public function deleteMember(int $idTeam, int $idMember) {
public function deleteMember(int $idTeam, int $idMember): void {
$this->con->exec(
"DELETE FROM Member WHERE idTeam = :idTeam AND idMember = :idMember",
[

@ -1,6 +1,6 @@
<?php
namespace App\Http;
namespace IQBall\Core\Http;
/**
* Utility class to define constants of used http codes

@ -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;

@ -1,6 +1,6 @@
<?php
namespace App\Http;
namespace IQBall\Core\Http;
class HttpResponse {
/**

@ -1,6 +1,6 @@
<?php
namespace App\Http;
namespace IQBall\Core\Http;
class JsonHttpResponse extends HttpResponse {
/**

@ -1,11 +1,11 @@
<?php
namespace App\Model;
namespace IQBall\Core\Model;
use App\Data\Account;
use App\Gateway\AccountGateway;
use App\Validation\FieldValidationFail;
use App\Validation\ValidationFail;
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;

@ -1,11 +1,10 @@
<?php
namespace App\Model;
namespace IQBall\Core\Model;
use App\Data\Account;
use App\Data\TacticInfo;
use App\Gateway\TacticInfoGateway;
use App\Validation\ValidationFail;
use IQBall\Core\Gateway\TacticInfoGateway;
use IQBall\Core\Validation\ValidationFail;
use IQBall\Core\Data\TacticInfo;
class TacticModel {
public const TACTIC_DEFAULT_NAME = "Nouvelle tactique";
@ -43,7 +42,7 @@ class TacticModel {
* @param integer $nb
* @return array<array<string,mixed>>
*/
public function getLast(int $nb) : ?array {
public function getLast(int $nb): ?array {
return $this->tactics->getLast($nb);
}

@ -1,12 +1,12 @@
<?php
namespace App\Model;
namespace IQBall\Core\Model;
use App\Gateway\TeamGateway;
use App\Data\Team;
use App\Data\Member;
use App\Data\MemberRole;
use App\Data\Color;
use IQBall\Core\Gateway\TeamGateway;
use IQBall\Core\Data\Team;
use IQBall\Core\Data\Member;
use IQBall\Core\Data\MemberRole;
use IQBall\Core\Data\Color;
class TeamModel {
private TeamGateway $gateway;
@ -20,18 +20,19 @@ class TeamModel {
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']);
return $this->gateway->getIdTeamByName($name);
}
public function addMember(string $mail, int $teamId, string $role) : int {
$result = $this->gateway->getMemberIdByMail($mail)[0];
$memberId = intval($result['id']);
$this->gateway->insertMember($teamId, $memberId, $role);
public function addMember(string $mail, int $teamId, string $role): int {
$id = $this->gateway->getMemberIdByMail($mail);
$this->gateway->insertMember($teamId, $id, $role);
return $teamId;
}
/**
* @param string $name
* @return Team[]
*/
public function listByName(string $name): array {
$teams = [];
$results = $this->gateway->listByName($name);
@ -43,7 +44,7 @@ class TeamModel {
public function displayTeam(int $id): Team {
$members = [];
$result = $this->gateway->getTeamById($id)[0];
$result = $this->gateway->getTeamById($id);
$resultMembers = $this->gateway->getMembersById($id);
foreach ($resultMembers as $row) {
var_dump($row['role']);
@ -57,9 +58,8 @@ class TeamModel {
return new Team(intval($result['id']), $result['name'], $result['picture'], Color::from($result['mainColor']), Color::from($result['secondColor']), $members);
}
public function deleteMember(string $mail, int $teamId) : int {
$result = $this->gateway->getMemberIdByMail($mail)[0];
$memberId = intval($result['id']);
public function deleteMember(string $mail, int $teamId): int {
$memberId = $this->gateway->getMemberIdByMail($mail);
$this->gateway->deleteMember($teamId, $memberId);
return $teamId;
}

@ -1,8 +1,8 @@
<?php
namespace App\Session;
namespace IQBall\Core\Session;
use App\Data\Account;
use IQBall\Core\Data\Account;
/**
* The mutable side of a session handle

@ -1,8 +1,8 @@
<?php
namespace App\Session;
namespace IQBall\Core\Session;
use App\Data\Account;
use IQBall\Core\Data\Account;
/**
* A PHP session handle

@ -1,8 +1,8 @@
<?php
namespace App\Session;
namespace IQBall\Core\Session;
use App\Data\Account;
use IQBall\Core\Data\Account;
/**
* An immutable session handle

@ -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;
use JsonSerializable;

@ -1,6 +1,6 @@
<?php
namespace App\Validation;
namespace IQBall\Core\Validation;
abstract class Validator {
/**

@ -1,6 +1,6 @@
<?php
namespace App\Validation;
namespace IQBall\Core\Validation;
/**
* A collection of standard validators

@ -1,9 +1,9 @@
<?php
namespace App\Validator;
namespace IQBall\Core\Validator;
use App\Data\TacticInfo;
use App\Validation\ValidationFail;
use IQBall\Core\Data\TacticInfo;
use IQBall\Core\Validation\ValidationFail;
class TacticValidator {
public static function validateAccess(?TacticInfo $tactic, int $tacticId, int $ownerId): ?ValidationFail {
Loading…
Cancel
Save