add and edit documentation, clean code
continuous-integration/drone/push Build is passing Details

pull/22/head
Override-6 1 year ago
parent 49cdcb8972
commit 89c3e72c8f
Signed by untrusted user who does not match committer: maxime.batista
GPG Key ID: 8002CC4B4DD9ECA5

@ -28,7 +28,7 @@ If we take a look at the request, we'll see that the url does not targets `local
`localhost:5173` is the react development server, it is able to serve our react front view files.
Let's run the react development server.
It is a simple as running `npm start` in a new terminal (be sure to run it in the repository's directory).
It is as simple as running `npm start` in a new terminal (be sure to run it in the repository's directory).
![](assets/npm-start.png)
You should see something like this, it says that the server was opened on port `5173`, thats our react development server !
@ -40,7 +40,7 @@ Caution: **NEVER** directly connect on the `localhost:5173` node development ser
# How it works
I'm glad you are interested in how that stuff works, it's a bit tricky, lets go.
If you look at our `index.php` (located in `/public` folder), you'll see that it is our gateway, it uses an `AltoRouter` that dispatches the request's process to a controller.
If you look at our `index.php` (located in `/public` folder), you'll see that it define our routes, it uses an `AltoRouter` then delegates the request's action processing to a controller.
We can see that there are two registered routes, the `GET:/` (that then calls `SampleFormController#displayForm()`) and `POST:/result` (that calls `SampleFormController#displayResults()`).
Implementation of those two methods are very simple: there is no verification to make nor model to control, thus they directly sends the view back to the client.
@ -115,7 +115,7 @@ function _asset(string $assetURI): string {
The simplest profile, simply redirect all assets to the development server
### Production profile
Before the CD deploys the generated files to the server,
Before the CD workflow step deploys the generated files to the server,
it generates a `/views-mappings.php` file that will map the react views file names to their compiled javascript files :
```php
@ -137,13 +137,4 @@ function _asset(string $assetURI): string {
// fallback to the uri itself.
return $basePath . "/" . (ASSETS[$assetURI] ?? $assetURI);
}
```
## React views conventions.
Conventions regarding our react views __must be respected in order to be renderable__.
### The `render(any)` function
Any React view component needs to be default exported in order to be imported and used from PHP. Those components will receive as props the arguments that the PHP server has transmitted.
The `arguments` parameter is used to pass data to the react component.
If you take a look at the `front/views/SampleForm.tsx` view, here's the definition of its render function :
```

@ -44,4 +44,8 @@ class ViewHttpResponse extends HttpResponse {
+ <u>react(file: string, arguments: array, code: int = HttpCodes::OK): ViewHttpResponse
}
note right of ViewHttpResponse
Into src/App
end note
@enduml

@ -1,45 +1,36 @@
@startuml
class Account {
- email: string
- phoneNumber: string
class TacticInfo {
- id: int
+ __construct (email : string, ???)
+ setMailAddress(string)
+ getMailAddress(): string
+ getPhoneNumber(): string
+ setPhoneNumber(string)
+ getUser(): AccountUser
- name: string
- creationDate: string
- ownerId: string
+ getId(): int
}
Account --> "- user" AccountUser
Account --> "- teams *" Team
interface User {
+ getOwnerId(): int
+ getCreationTimestamp(): int
+ getName(): string
+ getProfilePicture(): Url
+ getAge(): int
}
class AccountUser {
class Account {
- email: string
- token: string
- name: string
- profilePicture: Url
- age: int
- id: int
+ __construct(name : string, age : int, profilePicture : string)
+ setName(string)
+ setProfilePicture(URI)
+ setAge(int)
+ getMailAddress(): string
+ getToken(): string
+ getName(): string
+ getId(): int
}
AccountUser ..|> User
class Member {
- userId: int
- teamId: int
+ __construct(role : MemberRole)
+ getUserId(): int
+ getTeamId(): int
+ getRole(): MemberRole
}
@ -50,20 +41,27 @@ enum MemberRole {
COACH
}
class Team {
class TeamInfo {
- creationDate: int
- name: string
- picture: Url
- picture: string
+ __construct(name : string, picture : string, mainColor : Color, secondColor : Color,members : array)
+ getName(): string
+ getPicture(): Url
+ getPicture(): string
+ getMainColor(): Color
+ getSecondColor(): Color
+ listMembers(): array<Member>
}
Team --> "- mainColor" Color
Team --> "- secondColor" Color
TeamInfo --> "- mainColor" Color
TeamInfo --> "- secondaryColor" Color
class Team {
getInfo(): TeamInfo
listMembers(): Member[]
}
Team --> "- info" TeamInfo
Team --> "- members *" Member
class Color {
@ -72,33 +70,4 @@ class Color {
+ getValue(): int
}
class AuthController{
+ __construct(model : AuthModel)
+ displayRegister() : HttpResponse
+ displayBadFields(viewName : string, fails : array) : HttpResponse
+ confirmRegister(request : array) : HttpResponse
+ displayLogin() : HttpResponse
+ confirmLogin() : HttpResponse
}
AuthController --> "- model" AuthModel
class AuthModel{
+ __construct(gateway : AuthGateway)
+ register(username : string, password : string, confirmPassword : string, email : string): array
+ getAccount(email : string):array
+ login(email : string, password : string)
}
AuthModel --> "- gateway" AuthGateway
class AuthGateway{
-con : Connection
+ __construct (con : Connection)
+ mailExist(email : string) : bool
+ insertAccount(username : string, hash : string, email : string)
+ getHash(email : string):string
+ getAccount (email : string): array
}
@enduml

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

@ -58,12 +58,6 @@ class TeamController{
TeamController *--"- model" TeamModel
class Connexion{
- pdo : PDO
--
+ __constructor(pdo : PDO)
+ exec(query : string, args : array)
+ fetch(query string, args array): array
}
class Connexion { }
@enduml

@ -1,8 +0,0 @@
@startuml
class FrontController {
- router : AltoRouter
}
@enduml

@ -50,9 +50,11 @@ class Validation {
}
class Validators {
---
+ <u>nonEmpty(): Validator
+ <u>shorterThan(limit: int): Validator
+ <u>userString(maxLen: int): Validator
...
}

@ -13,16 +13,22 @@ use IQBall\App\Controller\EditorController;
use IQBall\App\Controller\TeamController;
use IQBall\App\Controller\UserController;
use IQBall\App\Controller\VisualizerController;
use IQBall\App\ViewHttpResponse;
use IQBall\Core\Action;
use IQBall\Core\Connection;
use IQBall\Core\Gateway\AccountGateway;
use IQBall\Core\Gateway\MemberGateway;
use IQBall\Core\Gateway\TacticInfoGateway;
use IQBall\Core\Gateway\TeamGateway;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Model\AuthModel;
use IQBall\Core\Model\TacticModel;
use IQBall\Core\Model\TeamModel;
use IQBall\Core\Session\MutableSessionHandle;
use IQBall\Core\Session\PhpSessionHandle;
use IQBall\Core\Session\SessionHandle;
use IQBall\Core\Validation\ValidationFail;
function getConnection(): Connection {
return new Connection(get_database());
@ -41,7 +47,8 @@ function getEditorController(): EditorController {
}
function getTeamController(): TeamController {
return new TeamController(new TeamModel(new TeamGateway(getConnection())));
$con = getConnection();
return new TeamController(new TeamModel(new TeamGateway($con), new MemberGateway($con), new AccountGateway($con)));
}
function getAuthController(): AuthController {
@ -57,8 +64,8 @@ function getRoutes(): AltoRouter {
//authentication
$ar->map("GET", "/login", Action::noAuth(fn() => getAuthController()->displayLogin()));
$ar->map("GET", "/register", Action::noAuth(fn() => getAuthController()->displayRegister()));
$ar->map("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmLogin($_POST, $s)));
$ar->map("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->confirmRegister($_POST, $s)));
$ar->map("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->login($_POST, $s)));
$ar->map("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->register($_POST, $s)));
//user-related
$ar->map("GET", "/home", Action::auth(fn(SessionHandle $s) => getUserController()->home($s)));
@ -66,8 +73,8 @@ function getRoutes(): AltoRouter {
$ar->map("GET", "/settings", Action::auth(fn(SessionHandle $s) => getUserController()->settings($s)));
//tactic-related
$ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->visualize($id, $s)));
$ar->map("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->edit($id, $s)));
$ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->openVisualizer($id, $s)));
$ar->map("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->openEditor($id, $s)));
$ar->map("GET", "/tactic/new", Action::auth(fn(SessionHandle $s) => getEditorController()->createNew($s)));
//team-related
@ -84,8 +91,19 @@ function getRoutes(): AltoRouter {
return $ar;
}
function runMatch($match, MutableSessionHandle $session): HttpResponse {
global $basePath;
if (!$match) {
return ViewHttpResponse::twig("error.html.twig", [
'failures' => [ValidationFail::notFound("Could not find page {$_SERVER['REQUEST_URI']}.")],
], HttpCodes::NOT_FOUND);
}
return App::runAction($basePath . '/login', $match['target'], $match['params'], $session);
}
//this is a global variable
$basePath = get_public_path();
App::render(App::runMatch(getRoutes()->match(), PhpSessionHandle::init()));
App::render(runMatch(getRoutes()->match(), PhpSessionHandle::init()), "../src/App/Views/");

@ -30,15 +30,15 @@ CREATE TABLE Team
id integer PRIMARY KEY AUTOINCREMENT,
name varchar,
picture varchar,
mainColor varchar,
secondColor varchar
main_color varchar,
second_color varchar
);
CREATE TABLE Member(
idTeam integer,
idMember integer,
role char(1) CHECK (role IN ('C', 'P')),
FOREIGN KEY (idTeam) REFERENCES Team (id),
FOREIGN KEY (idMember) REFERENCES User (id)
id_team integer,
id_user integer,
role char(1) CHECK (role IN ('Coach', 'Player')),
FOREIGN KEY (id_team) REFERENCES Team (id),
FOREIGN KEY (id_user) REFERENCES User (id)
);

@ -2,6 +2,7 @@
namespace IQBall\Api\Controller;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Route\Control;
use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse;
@ -20,16 +21,20 @@ class APIAuthController {
}
/**
* From given email address and password, authenticate the user and respond with its authorization token.
* @return HttpResponse
*/
public function authorize(): HttpResponse {
return Control::runChecked([
"email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)],
"email" => [Validators::email(), Validators::lenBetween(5, 256)],
"password" => [Validators::lenBetween(6, 256)],
], function (HttpRequest $req) {
$failures = [];
$account = $this->model->login($req["email"], $req["password"], $failures);
if (!empty($failures)) {
return new JsonHttpResponse($failures);
return new JsonHttpResponse($failures, HttpCodes::UNAUTHORIZED);
}
return new JsonHttpResponse(["authorization" => $account->getToken()]);

@ -24,6 +24,12 @@ class APITacticController {
$this->model = $model;
}
/**
* update name of tactic, specified by tactic identifier, given in url.
* @param int $tactic_id
* @param Account $account
* @return HttpResponse
*/
public function updateName(int $tactic_id, Account $account): HttpResponse {
return Control::runChecked([
"name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()],

@ -3,11 +3,9 @@
namespace IQBall\App;
use IQBall\Core\Action;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Session\MutableSessionHandle;
use IQBall\Core\Validation\ValidationFail;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
@ -15,7 +13,16 @@ use Twig\Error\SyntaxError;
use Twig\Loader\FilesystemLoader;
class App {
public static function render(HttpResponse $response): void {
/**
* renders (prints out) given HttpResponse to the client
* @param HttpResponse $response
* @param string $twigViewsFolder
* @return void
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*/
public static function render(HttpResponse $response, string $twigViewsFolder): void {
http_response_code($response->getCode());
foreach ($response->getHeaders() as $header => $value) {
@ -23,14 +30,23 @@ class App {
}
if ($response instanceof ViewHttpResponse) {
self::renderView($response);
self::renderView($response, $twigViewsFolder);
} elseif ($response instanceof JsonHttpResponse) {
header('Content-type: application/json');
echo $response->getJson();
}
}
private static function renderView(ViewHttpResponse $response): void {
/**
* renders (prints out) given ViewHttpResponse to the client
* @param ViewHttpResponse $response
* @param string $twigViewsFolder
* @return void
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*/
private static function renderView(ViewHttpResponse $response, string $twigViewsFolder): void {
$file = $response->getFile();
$args = $response->getArguments();
@ -40,8 +56,8 @@ class App {
break;
case ViewHttpResponse::TWIG_VIEW:
try {
$loader = new FilesystemLoader('../src/App/Views/');
$twig = new Environment($loader);
$fl = new FilesystemLoader($twigViewsFolder);
$twig = new Environment($fl);
$twig->display($file, $args);
} catch (RuntimeError | SyntaxError | LoaderError $e) {
http_response_code(500);
@ -53,38 +69,25 @@ class App {
}
/**
* run a user action, and return the generated response
* @param string $authRoute the route towards an authentication page to response with a redirection
* if the run action requires auth but session does not contain a logged-in account.
* @param Action<MutableSessionHandle> $action
* @param mixed[] $params
* @param MutableSessionHandle $session
* @return HttpResponse
*/
private static function runAction(Action $action, array $params, MutableSessionHandle $session): HttpResponse {
global $basePath;
public static function runAction(string $authRoute, Action $action, array $params, MutableSessionHandle $session): HttpResponse {
if ($action->isAuthRequired()) {
$account = $session->getAccount();
if ($account == null) {
// put in the session the initial url the user wanted to get
$session->setInitialTarget($_SERVER['REQUEST_URI']);
return HttpResponse::redirect($basePath . "/login");
return HttpResponse::redirect($authRoute);
}
}
return $action->run($params, $session);
}
/**
* @param array<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);
}
}

@ -25,33 +25,25 @@ class AuthController {
}
/**
* @param string $viewName
* @param ValidationFail[] $fails
* @return HttpResponse
*/
private function displayBadFields(string $viewName, array $fails): HttpResponse {
return ViewHttpResponse::twig($viewName, ['fails' => $fails]);
}
/**
* registers given account
* @param mixed[] $request
* @param MutableSessionHandle $session
* @return HttpResponse
*/
public function confirmRegister(array $request, MutableSessionHandle $session): HttpResponse {
public function register(array $request, MutableSessionHandle $session): HttpResponse {
$fails = [];
$request = HttpRequest::from($request, $fails, [
"username" => [Validators::name(), Validators::lenBetween(2, 32)],
"password" => [Validators::lenBetween(6, 256)],
"confirmpassword" => [Validators::lenBetween(6, 256)],
"email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/", "invalide"),Validators::lenBetween(5, 256)],
"email" => [Validators::email(), Validators::lenBetween(5, 256)],
]);
if (!empty($fails)) {
return $this->displayBadFields("display_register.html.twig", $fails);
return ViewHttpResponse::twig("display_register.html.twig", ['fails' => $fails]);
}
$account = $this->model->register($request['username'], $request["password"], $request['confirmpassword'], $request['email'], $fails);
if (!empty($fails)) {
return $this->displayBadFields("display_register.html.twig", $fails);
return ViewHttpResponse::twig("display_register.html.twig", ['fails' => $fails]);
}
$session->setAccount($account);
@ -66,22 +58,24 @@ class AuthController {
}
/**
* logins given account credentials
* @param mixed[] $request
* @param MutableSessionHandle $session
* @return HttpResponse
*/
public function confirmLogin(array $request, MutableSessionHandle $session): HttpResponse {
public function login(array $request, MutableSessionHandle $session): HttpResponse {
$fails = [];
$request = HttpRequest::from($request, $fails, [
"password" => [Validators::lenBetween(6, 256)],
"email" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/", "invalide"),Validators::lenBetween(5, 256)],
"email" => [Validators::email(), Validators::lenBetween(5, 256)],
]);
if (!empty($fails)) {
return $this->displayBadFields("display_login.html.twig", $fails);
return ViewHttpResponse::twig("display_login.html.twig", ['fails' => $fails]);
}
$account = $this->model->login($request['email'], $request['password'], $fails);
if (!empty($fails)) {
return $this->displayBadFields("display_login.html.twig", $fails);
return ViewHttpResponse::twig("display_login.html.twig", ['fails' => $fails]);
}
$session->setAccount($account);

@ -17,22 +17,31 @@ class EditorController {
$this->model = $model;
}
private function openEditor(TacticInfo $tactic): HttpResponse {
/**
* @param TacticInfo $tactic
* @return ViewHttpResponse the editor view for given tactic
*/
private function openEditorFor(TacticInfo $tactic): ViewHttpResponse {
return ViewHttpResponse::react("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]);
}
public function createNew(SessionHandle $session): HttpResponse {
/**
* creates a new empty tactic, with default name
* @param SessionHandle $session
* @return ViewHttpResponse the editor view
*/
public function createNew(SessionHandle $session): ViewHttpResponse {
$tactic = $this->model->makeNewDefault($session->getAccount()->getId());
return $this->openEditor($tactic);
return $this->openEditorFor($tactic);
}
/**
* returns an editor view for a given tactic
* @param int $id the targeted tactic identifier
* @param SessionHandle $session
* @return HttpResponse
* @return ViewHttpResponse
*/
public function edit(int $id, SessionHandle $session): HttpResponse {
public function openEditor(int $id, SessionHandle $session): ViewHttpResponse {
$tactic = $this->model->get($id);
$failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId());
@ -41,7 +50,7 @@ class EditorController {
return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND);
}
return $this->openEditor($tactic);
return $this->openEditorFor($tactic);
}

@ -20,20 +20,34 @@ class TeamController {
$this->model = $model;
}
public function displayCreateTeam(SessionHandle $session): HttpResponse {
/**
* @param SessionHandle $session
* @return ViewHttpResponse the team creation panel
*/
public function displayCreateTeam(SessionHandle $session): ViewHttpResponse {
return ViewHttpResponse::twig("insert_team.html.twig", []);
}
public function displayAddMember(SessionHandle $session): HttpResponse {
/**
* @param SessionHandle $session
* @return ViewHttpResponse the team panel to add a member
*/
public function displayAddMember(SessionHandle $session): ViewHttpResponse {
return ViewHttpResponse::twig("add_member.html.twig", []);
}
public function displayDeleteMember(SessionHandle $session): HttpResponse {
/**
* @param SessionHandle $session
* @return ViewHttpResponse the team panel to delete a member
*/
public function displayDeleteMember(SessionHandle $session): ViewHttpResponse {
return ViewHttpResponse::twig("delete_member.html.twig", []);
}
/**
* create a new team from given request name, mainColor, secondColor and picture url
* @param array<string, mixed> $request
* @param SessionHandle $session
* @return HttpResponse
@ -42,8 +56,8 @@ class TeamController {
$failures = [];
$request = HttpRequest::from($request, $failures, [
"name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()],
"mainColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')],
"secondColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')],
"main_color" => [Validators::hexColor()],
"second_color" => [Validators::hexColor()],
"picture" => [Validators::isURL()],
]);
if (!empty($failures)) {
@ -55,15 +69,22 @@ class TeamController {
}
return ViewHttpResponse::twig('insert_team.html.twig', ['bad_fields' => $badFields]);
}
return $this->displayTeam($this->model->createTeam($request['name'], $request['picture'], $request['mainColor'], $request['secondColor']), $session);
$teamId = $this->model->createTeam($request['name'], $request['picture'], $request['main_color'], $request['second_color']);
return $this->displayTeam($teamId, $session);
}
public function displayListTeamByName(SessionHandle $session): HttpResponse {
/**
* @param SessionHandle $session
* @return ViewHttpResponse the panel to search a team by its name
*/
public function displayListTeamByName(SessionHandle $session): ViewHttpResponse {
return ViewHttpResponse::twig("list_team_by_name.html.twig", []);
}
/**
* @param array<string , mixed> $request
* 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 {
@ -77,21 +98,27 @@ class TeamController {
return ViewHttpResponse::twig('list_team_by_name.html.twig', ['bad_field' => $badField]);
}
$results = $this->model->listByName($request['name']);
$teams = $this->model->listByName($request['name']);
if (empty($results)) {
if (empty($teams)) {
return ViewHttpResponse::twig('display_teams.html.twig', []);
}
return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $results]);
return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $teams]);
}
public function displayTeam(int $id, SessionHandle $session): HttpResponse {
$result = $this->model->displayTeam($id);
/**
* @param int $id
* @param SessionHandle $session
* @return ViewHttpResponse a view that displays given team information
*/
public function displayTeam(int $id, SessionHandle $session): ViewHttpResponse {
$result = $this->model->getTeam($id);
return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]);
}
/**
* add a member to a team
* @param array<string, mixed> $request
* @param SessionHandle $session
* @return HttpResponse
@ -101,13 +128,16 @@ class TeamController {
$request = HttpRequest::from($request, $errors, [
"team" => [Validators::isInteger()],
"mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)],
"email" => [Validators::email(), Validators::lenBetween(5, 256)],
]);
return $this->displayTeam($this->model->addMember($request['mail'], intval($request['team']), $request['role']), $session);
$teamId = intval($request['team']);
$this->model->addMember($request['email'], $teamId, $request['role']);
return $this->displayTeam($teamId, $session);
}
/**
* remove a member from a team
* @param array<string, mixed> $request
* @param SessionHandle $session
* @return HttpResponse
@ -117,9 +147,9 @@ class TeamController {
$request = HttpRequest::from($request, $errors, [
"team" => [Validators::isInteger()],
"mail" => [Validators::regex("/^\\S+@\\S+\\.\\S+$/"), Validators::lenBetween(5, 256)],
"email" => [Validators::email(), Validators::lenBetween(5, 256)],
]);
return $this->displayTeam($this->model->deleteMember($request['mail'], intval($request['team'])), $session);
return $this->displayTeam($this->model->deleteMember($request['email'], intval($request['team'])), $session);
}
}

@ -19,18 +19,18 @@ class UserController {
/**
* @param SessionHandle $session
* @return HttpResponse the home page
* @return ViewHttpResponse the home page view
*/
public function home(SessionHandle $session): HttpResponse {
//TODO use session's account to get the last 5 tactics if the logged-in account
public function home(SessionHandle $session): ViewHttpResponse {
//TODO use session's account to get the last 5 tactics of the logged-in account
$listTactic = $this->tactics->getLast(5);
return ViewHttpResponse::twig("home.twig", ["recentTactic" => $listTactic]);
}
/**
* @return HttpResponse account settings page
* @return ViewHttpResponse account settings page
*/
public function settings(SessionHandle $session): HttpResponse {
public function settings(SessionHandle $session): ViewHttpResponse {
return ViewHttpResponse::twig("account_settings.twig", []);
}

@ -19,7 +19,13 @@ class VisualizerController {
$this->tacticModel = $tacticModel;
}
public function visualize(int $id, SessionHandle $session): HttpResponse {
/**
* opens a visualisation page for the tactic specified by its identifier in the url.
* @param int $id
* @param SessionHandle $session
* @return HttpResponse
*/
public function openVisualizer(int $id, SessionHandle $session): HttpResponse {
$tactic = $this->tacticModel->get($id);
$failure = TacticValidator::validateAccess($tactic, $id, $session->getAccount()->getId());

@ -20,17 +20,13 @@
height:50px;
}
#mainColor{
background-color: {{ team.mainColor.getValue() }};
{% if team.mainColor.getValue() == "#ffffff" %}
border-color: #666666;
{% endif %}
#main_color {
border: solid;
background-color: {{ team.getInfo().getMainColor().getValue() }};
}
#secondColor{
background-color: {{ team.secondColor.getValue() }};
{% if team.secondColor.getValue() == "#ffffff" %}
border-color: #666666;
{% endif %}
#second_color{
background-color: {{ team.getInfo().getSecondColor().getValue() }};
border: solid;
}
.container{
@ -66,12 +62,12 @@
<div class="team container">
<div>
<h1>{{ team.name }}</h1>
<img src="{{ team.picture }}" alt="Logo d'équipe" class="logo">
<h1>{{ team.getInfo().getName() }}</h1>
<img src="{{ team.getInfo().getPicture() }}" alt="Logo d'équipe" class="logo">
</div>
<div>
<div class="color"><p>Couleur principale : </p><div class="square" id="mainColor"></div> </div>
<div class="color"><p>Couleur secondaire : </p><div class="square" id="secondColor"></div></div>
<div class="color"><p>Couleur principale : </p><div class="square" id="main_color"></div> </div>
<div class="color"><p>Couleur secondaire : </p><div class="square" id="second_color"></div></div>
</div>
{% for m in team.listMembers() %}

@ -70,10 +70,10 @@
<input type="text" id="name" name="name" required>
<label for= "picture">Logo:</label>
<input type="text" id="picture" name="picture" required >
<label for="mainColor">Couleur principale</label>
<input type="color" id="mainColor" name="mainColor" required>
<label for="secondColor">Couleur secondaire</label>
<input type="color" id="secondColor" name="secondColor" required>
<label for="main_color">Couleur principale</label>
<input type="color" id="main_color" name="main_color" required>
<label for="second_color">Couleur secondaire</label>
<input type="color" id="second_color" name="second_color" required>
</div>
<div class="form-group">
<input type="submit" value="Confirmer">

@ -5,6 +5,7 @@ namespace IQBall\Core;
use IQBall\Core\Http\HttpResponse;
/**
* Represent an action.
* @template S session
*/
class Action {
@ -28,6 +29,7 @@ class Action {
}
/**
* Runs an action
* @param mixed[] $params
* @param S $session
* @return HttpResponse

@ -1,52 +0,0 @@
<?php
namespace IQBall\Core\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;
}
}

@ -7,7 +7,7 @@ use InvalidArgumentException;
/**
* Enumeration class workaround
* As there is no enumerations in php 7.4, this class
* encapsulates an integer value and use it as an enumeration discriminant
* encapsulates an integer value and use it as a variant discriminant
*/
final class MemberRole {
private const ROLE_PLAYER = 0;
@ -32,6 +32,27 @@ final class MemberRole {
return new MemberRole(MemberRole::ROLE_COACH);
}
public function name(): string {
switch ($this->value) {
case self::ROLE_COACH:
return "Coach";
case self::ROLE_PLAYER:
return "Player";
}
die("unreachable");
}
public static function fromName(string $name): ?MemberRole {
switch ($name) {
case "Coach":
return MemberRole::coach();
case "Player":
return MemberRole::player();
default:
return null;
}
}
private function isValid(int $val): bool {
return ($val <= self::MAX and $val >= self::MIN);
}

@ -2,24 +2,23 @@
namespace IQBall\Core\Data;
class TacticInfo implements \JsonSerializable {
class TacticInfo {
private int $id;
private string $name;
private int $creation_date;
private int $creationDate;
private int $ownerId;
/**
* @param int $id
* @param string $name
* @param int $creation_date
* @param int $creationDate
* @param int $ownerId
*/
public function __construct(int $id, string $name, int $creation_date, int $ownerId) {
public function __construct(int $id, string $name, int $creationDate, int $ownerId) {
$this->id = $id;
$this->name = $name;
$this->ownerId = $ownerId;
$this->creation_date = $creation_date;
$this->creationDate = $creationDate;
}
public function getId(): int {
@ -38,13 +37,7 @@ class TacticInfo implements \JsonSerializable {
}
public function getCreationTimestamp(): int {
return $this->creation_date;
return $this->creationDate;
}
/**
* @return array<string, mixed>
*/
public function jsonSerialize(): array {
return get_object_vars($this);
}
}

@ -3,11 +3,7 @@
namespace IQBall\Core\Data;
class Team {
private int $id;
private string $name;
private string $picture;
private Color $mainColor;
private Color $secondColor;
private TeamInfo $info;
/**
* @var Member[] maps users with their role
@ -15,54 +11,16 @@ class Team {
private array $members;
/**
* @param string $name
* @param string $picture
* @param Color $mainColor
* @param Color $secondColor
* @param TeamInfo $info
* @param Member[] $members
*/
public function __construct(int $id, string $name, string $picture, Color $mainColor, Color $secondColor, array $members = []) {
$this->id = $id;
$this->name = $name;
$this->picture = $picture;
$this->mainColor = $mainColor;
$this->secondColor = $secondColor;
public function __construct(TeamInfo $info, array $members = []) {
$this->info = $info;
$this->members = $members;
}
/**
* @return int
*/
public function getId(): int {
return $this->id;
}
/**
* @return string
*/
public function getName(): string {
return $this->name;
}
/**
* @return string
*/
public function getPicture(): string {
return $this->picture;
}
/**
* @return Color
*/
public function getMainColor(): Color {
return $this->mainColor;
}
/**
* @return Color
*/
public function getSecondColor(): Color {
return $this->secondColor;
public function getInfo(): TeamInfo {
return $this->info;
}
/**
@ -71,9 +29,4 @@ class Team {
public function listMembers(): array {
return $this->members;
}
public function addMember(Member $m): void {
$this->members[] = $m;
}
}

@ -0,0 +1,49 @@
<?php
namespace IQBall\Core\Data;
class TeamInfo {
private int $id;
private string $name;
private string $picture;
private Color $mainColor;
private Color $secondColor;
/**
* @param int $id
* @param string $name
* @param string $picture
* @param Color $mainColor
* @param Color $secondColor
*/
public function __construct(int $id, string $name, string $picture, Color $mainColor, Color $secondColor) {
$this->id = $id;
$this->name = $name;
$this->picture = $picture;
$this->mainColor = $mainColor;
$this->secondColor = $secondColor;
}
public function getId(): int {
return $this->id;
}
public function getName(): string {
return $this->name;
}
public function getPicture(): string {
return $this->picture;
}
public function getMainColor(): Color {
return $this->mainColor;
}
public function getSecondColor(): Color {
return $this->secondColor;
}
}

@ -1,26 +0,0 @@
<?php
namespace IQBall\Core\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;
}

@ -35,7 +35,10 @@ class AccountGateway {
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) {
@ -44,6 +47,10 @@ class AccountGateway {
return $results['hash'];
}
/**
* @param string $email
* @return bool true if the given email exists in the database
*/
public function exists(string $email): bool {
return $this->getRowsFromMail($email) != null;
}
@ -61,6 +68,10 @@ class AccountGateway {
return new Account($email, $acc["username"], $acc["token"], $acc["id"]);
}
/**
* @param string $token get an account from given token
* @return Account|null
*/
public function getAccountFromToken(string $token): ?Account {
$acc = $this->con->fetch("SELECT * FROM Account WHERE token = :token", [':token' => [$token, PDO::PARAM_STR]])[0] ?? null;
if (empty($acc)) {

@ -1,47 +0,0 @@
<?php
namespace IQBall\Core\Gateway;
use IQBall\Core\Connection;
use PDO;
class AuthGateway {
private Connection $con;
/**
* @param Connection $con
*/
public function __construct(Connection $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(username, hash, email) VALUES (:username,:hash,:email)", [':username' => [$username, PDO::PARAM_STR],':hash' => [$hash, PDO::PARAM_STR],':email' => [$email, PDO::PARAM_STR]]);
}
public function getUserHash(string $email): string {
$results = $this->con->fetch("SELECT hash FROM AccountUser WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]]);
return $results[0]['hash'];
}
/**
* @param string $email
* @return array<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;
}
}

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

@ -16,6 +16,11 @@ class TacticInfoGateway {
$this->con = $con;
}
/**
* get tactic information from given identifier
* @param int $id
* @return TacticInfo|null
*/
public function get(int $id): ?TacticInfo {
$res = $this->con->fetch(
"SELECT * FROM Tactic WHERE id = :id",
@ -49,6 +54,11 @@ class TacticInfoGateway {
return $res;
}
/**
* @param string $name
* @param int $owner
* @return TacticInfo
*/
public function insert(string $name, int $owner): TacticInfo {
$this->con->exec(
"INSERT INTO Tactic(name, owner) VALUES(:name, :owner)",
@ -64,6 +74,12 @@ class TacticInfoGateway {
return new TacticInfo(intval($row["id"]), $name, strtotime($row["creation_date"]), $row["owner"]);
}
/**
* update name of given tactic identifier
* @param int $id
* @param string $name
* @return void
*/
public function updateName(int $id, string $name): void {
$this->con->exec(
"UPDATE Tactic SET name = :name WHERE id = :id",

@ -3,6 +3,8 @@
namespace IQBall\Core\Gateway;
use IQBall\Core\Connection;
use IQBall\Core\Data\Color;
use IQBall\Core\Data\TeamInfo;
use PDO;
class TeamGateway {
@ -12,62 +14,65 @@ class TeamGateway {
$this->con = $con;
}
public function insert(string $name, string $picture, string $mainColor, string $secondColor): void {
/**
* @param string $name
* @param string $picture
* @param string $mainColor
* @param string $secondColor
* @return int the inserted team identifier
*/
public function insert(string $name, string $picture, string $mainColor, string $secondColor): int {
$this->con->exec(
"INSERT INTO Team(name, picture, mainColor, secondColor) VALUES (:teamName , :picture, :mainColor, :secondColor)",
"INSERT INTO Team(name, picture, main_color, second_color) VALUES (:team_name , :picture, :main_color, :second_color)",
[
":teamName" => [$name, PDO::PARAM_STR],
":team_name" => [$name, PDO::PARAM_STR],
":picture" => [$picture, PDO::PARAM_STR],
":mainColor" => [$mainColor, PDO::PARAM_STR],
":secondColor" => [$secondColor, PDO::PARAM_STR],
]
);
}
public function insertMember(int $idTeam, int $idMember, string $role): void {
$this->con->exec(
"INSERT INTO Member(idTeam, idMember, role) VALUES (:idTeam , :idMember, :role)",
[
":idTeam" => [$idTeam, PDO::PARAM_INT],
":idMember" => [$idMember, PDO::PARAM_INT],
":role" => [$role, PDO::PARAM_STR],
":main_color" => [$mainColor, PDO::PARAM_STR],
":second_color" => [$secondColor, PDO::PARAM_STR],
]
);
return intval($this->con->lastInsertId());
}
/**
* @param string $name
* @return array<string, mixed>[]
* @return TeamInfo[]
*/
public function listByName(string $name): array {
return $this->con->fetch(
"SELECT id,name,picture,mainColor,secondColor FROM Team WHERE name LIKE '%' || :name || '%'",
$result = $this->con->fetch(
"SELECT * FROM Team WHERE name LIKE '%' || :name || '%'",
[
":name" => [$name, PDO::PARAM_STR],
]
);
return array_map(fn($row) => new TeamInfo($row['id'], $row['name'], $row['picture'], Color::from($row['main_color']), Color::from($row['second_color'])), $result);
}
/**
* @param int $id
* @return array<string,mixed>
* @return TeamInfo
*/
public function getTeamById(int $id): ?array {
return $this->con->fetch(
"SELECT id,name,picture,mainColor,secondColor FROM Team WHERE id = :id",
public function getTeamById(int $id): ?TeamInfo {
$row = $this->con->fetch(
"SELECT * FROM Team WHERE id = :id",
[
":id" => [$id, PDO::PARAM_INT],
]
)[0] ?? null;
if ($row == null) {
return null;
}
return new TeamInfo($row['id'], $row['name'], $row['picture'], Color::from($row['main_color']), Color::from($row['second_color']));
}
/**
* @param string $name
* @return int|null
*/
public function getIdTeamByName(string $name): ?int {
public function getTeamIdByName(string $name): ?int {
return $this->con->fetch(
"SELECT id FROM Team WHERE name = :name",
[
@ -76,40 +81,5 @@ class TeamGateway {
)[0]['id'] ?? null;
}
/**
* @param int $id
* @return array<string,mixed>[]
*/
public function getMembersById(int $id): array {
return $this->con->fetch(
"SELECT a.id,m.role,a.email,a.username FROM Account a,Team t,Member m WHERE t.id = :id AND m.idTeam = t.id AND m.idMember = a.id",
[
":id" => [$id, PDO::PARAM_INT],
]
);
}
/**
* @param string $mail
* @return int|null
*/
public function getMemberIdByMail(string $mail): ?int {
return $this->con->fetch(
"SELECT id FROM Account WHERE email = :mail",
[
":mail" => [$mail, PDO::PARAM_STR],
]
)[0]['id'] ?? null;
}
public function deleteMember(int $idTeam, int $idMember): void {
$this->con->exec(
"DELETE FROM Member WHERE idTeam = :idTeam AND idMember = :idMember",
[
":idTeam" => [$idTeam, PDO::PARAM_INT],
":idMember" => [$idMember, PDO::PARAM_INT],
]
);
}
}

@ -9,6 +9,7 @@ class HttpCodes {
public const OK = 200;
public const FOUND = 302;
public const BAD_REQUEST = 400;
public const UNAUTHORIZED = 401;
public const FORBIDDEN = 403;

@ -29,10 +29,19 @@ class HttpResponse {
return $this->headers;
}
/**
* @param int $code
* @return HttpResponse
*/
public static function fromCode(int $code): HttpResponse {
return new HttpResponse($code, []);
}
/**
* @param string $url the url to redirect
* @param int $code only HTTP 3XX codes are accepted.
* @return HttpResponse a response that will redirect client to given url
*/
public static function redirect(string $url, int $code = HttpCodes::FOUND): HttpResponse {
if ($code < 300 || $code >= 400) {
throw new \InvalidArgumentException("given code is not a redirection http code");

@ -47,6 +47,10 @@ class AuthModel {
return new Account($email, $username, $token, $accountId);
}
/**
* Generate a random base 64 string
* @return string
*/
private function generateToken(): string {
return base64_encode(random_bytes(64));
}
@ -58,11 +62,11 @@ class AuthModel {
* @return Account|null the authenticated account or null if failures occurred
*/
public function login(string $email, string $password, array &$failures): ?Account {
if (!$this->gateway->exists($email)) {
$failures[] = new FieldValidationFail("email", "Vous n'êtes pas enregistré.");
$hash = $this->gateway->getHash($email);
if ($hash == null) {
$failures[] = new FieldValidationFail("email", "l'addresse email n'est pas connue.");
return null;
}
$hash = $this->gateway->getHash($email);
if (!password_verify($password, $hash)) {
$failures[] = new FieldValidationFail("password", "Mot de passe invalide.");

@ -19,12 +19,23 @@ class TacticModel {
$this->tactics = $tactics;
}
/**
* creates a new empty tactic, with given name
* @param string $name
* @param int $ownerId
* @return TacticInfo
*/
public function makeNew(string $name, int $ownerId): TacticInfo {
return $this->tactics->insert($name, $ownerId);
}
/**
* creates a new empty tactic, with a default name
* @param int $ownerId
* @return TacticInfo|null
*/
public function makeNewDefault(int $ownerId): ?TacticInfo {
return $this->tactics->insert(self::TACTIC_DEFAULT_NAME, $ownerId);
return $this->makeNew(self::TACTIC_DEFAULT_NAME, $ownerId);
}
/**

@ -2,65 +2,80 @@
namespace IQBall\Core\Model;
use IQBall\Core\Gateway\TeamGateway;
use IQBall\Core\Data\Team;
use IQBall\Core\Data\Member;
use IQBall\Core\Data\MemberRole;
use IQBall\Core\Data\Color;
use IQBall\Core\Data\Team;
use IQBall\Core\Data\TeamInfo;
use IQBall\Core\Gateway\AccountGateway;
use IQBall\Core\Gateway\MemberGateway;
use IQBall\Core\Gateway\TeamGateway;
class TeamModel {
private TeamGateway $gateway;
private AccountGateway $users;
private TeamGateway $teams;
private MemberGateway $members;
/**
* @param TeamGateway $gateway
* @param MemberGateway $members
* @param AccountGateway $users
*/
public function __construct(TeamGateway $gateway) {
$this->gateway = $gateway;
public function __construct(TeamGateway $gateway, MemberGateway $members, AccountGateway $users) {
$this->teams = $gateway;
$this->members = $members;
$this->users = $users;
}
/**
* @param string $name
* @param string $picture
* @param string $mainColor
* @param string $secondColor
* @return int
*/
public function createTeam(string $name, string $picture, string $mainColor, string $secondColor): int {
$this->gateway->insert($name, $picture, $mainColor, $secondColor);
return $this->gateway->getIdTeamByName($name);
return $this->teams->insert($name, $picture, $mainColor, $secondColor);
}
public function addMember(string $mail, int $teamId, string $role): int {
$id = $this->gateway->getMemberIdByMail($mail);
$this->gateway->insertMember($teamId, $id, $role);
return $teamId;
/**
* adds a member to a team
* @param string $mail
* @param int $teamId
* @param string $role
* @return void
*/
public function addMember(string $mail, int $teamId, string $role): void {
$userId = $this->users->getAccountFromMail($mail)->getId();
$this->members->insert($teamId, $userId, $role);
}
/**
* @param string $name
* @return Team[]
* @return TeamInfo[]
*/
public function listByName(string $name): array {
$teams = [];
$results = $this->gateway->listByName($name);
foreach ($results as $row) {
$teams[] = new Team($row['id'], $row['name'], $row['picture'], Color::from($row['mainColor']), Color::from($row['secondColor']));
}
return $teams;
return $this->teams->listByName($name);
}
public function displayTeam(int $id): Team {
$members = [];
$result = $this->gateway->getTeamById($id);
$resultMembers = $this->gateway->getMembersById($id);
foreach ($resultMembers as $row) {
var_dump($row['role']);
if ($row['role'] == 'C') {
$role = MemberRole::coach();
} else {
$role = MemberRole::player();
}
$members[] = new Member($row['id'], $id, $role);
}
return new Team(intval($result['id']), $result['name'], $result['picture'], Color::from($result['mainColor']), Color::from($result['secondColor']), $members);
/**
* @param int $id
* @return Team
*/
public function getTeam(int $id): Team {
$teamInfo = $this->teams->getTeamById($id);
$members = $this->members->getMembersOfTeam($id);
return new Team($teamInfo, $members);
}
/**
* delete a member from given team identifier
* @param string $mail
* @param int $teamId
* @return int
*/
public function deleteMember(string $mail, int $teamId): int {
$memberId = $this->gateway->getMemberIdByMail($mail);
$this->gateway->deleteMember($teamId, $memberId);
$userId = $this->users->getAccountFromMail($mail)->getId();
$this->members->remove($teamId, $userId);
return $teamId;
}

@ -33,10 +33,18 @@ class ValidationFail implements JsonSerializable {
return ["error" => $this->kind, "message" => $this->message];
}
/**
* @param string $message
* @return ValidationFail validation fail for unknown resource access
*/
public static function notFound(string $message): ValidationFail {
return new ValidationFail("Not found", $message);
}
/**
* @param string $message
* @return ValidationFail validation fail for unauthorized accesses
*/
public static function unauthorized(string $message = "Unauthorized"): ValidationFail {
return new ValidationFail("Unauthorized", $message);
}

@ -12,10 +12,18 @@ class Validators {
public static function regex(string $regex, ?string $msg = null): Validator {
return new SimpleFunctionValidator(
fn(string $str) => preg_match($regex, $str),
fn(string $name) => [new FieldValidationFail($name, $msg == null ? "field does not validates pattern $regex" : $msg)]
fn(string $name) => [new FieldValidationFail($name, $msg == null ? "le champ ne valide pas le pattern $regex" : $msg)]
);
}
public static function hex(?string $msg = null): Validator {
return self::regex('/#([0-9a-fA-F])/', $msg == null ? "le champ n'est pas un nombre hexadecimal valide" : $msg);
}
public static function hexColor(?string $msg = null): Validator {
return self::regex('/#([0-9a-fA-F]{6})/', $msg == null ? "le champ n'est pas une couleur valide" : $msg);
}
/**
* @return Validator a validator that validates strings that only contains numbers, letters, accents letters, `-` and `_`.
*/
@ -51,6 +59,13 @@ class Validators {
);
}
public static function email(?string $msg = null): Validator {
return new SimpleFunctionValidator(
fn(string $str) => filter_var($str, FILTER_VALIDATE_EMAIL),
fn(string $name) => [new FieldValidationFail($name, $msg == null ? "addresse mail invalide" : $msg)]
);
}
public static function isInteger(): Validator {
return self::regex("/^[0-9]+$/");

@ -6,6 +6,12 @@ use IQBall\Core\Data\TacticInfo;
use IQBall\Core\Validation\ValidationFail;
class TacticValidator {
/**
* @param TacticInfo|null $tactic
* @param int $tacticId
* @param int $ownerId
* @return ValidationFail|null
*/
public static function validateAccess(?TacticInfo $tactic, int $tacticId, int $ownerId): ?ValidationFail {
if ($tactic == null) {
return ValidationFail::notFound("La tactique $tacticId n'existe pas");
@ -17,5 +23,4 @@ class TacticValidator {
return null;
}
}

Loading…
Cancel
Save