diff --git a/.gitignore b/.gitignore index 1c4404c..61df6e7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ .vite vendor - +.nfs* composer.lock *.phar /dist diff --git a/Documentation/team.puml b/Documentation/team.puml new file mode 100644 index 0000000..a291b1d --- /dev/null +++ b/Documentation/team.puml @@ -0,0 +1,68 @@ +@startuml +class Team { + - name: String + - picture: Url + - members: array + + + getName(): String + + getPicture(): Url + + getMainColor(): Color + + getSecondColor(): Color + + listMembers(): array +} + +Team --> "- mainColor" Color +Team --> "- secondColor" Color + +class Color { + - value: string + - __construct(value : string) + + getValue(): string + + from(value: string): Color + + tryFrom(value : string) : ?Color +} + +class TeamGateway{ + -- + + __construct(con : Connexion) + + insert(name : string ,picture : string, mainColor : Color, secondColor : Color) + + listByName(name : string): array +} + +TeamGateway *--"- con" Connexion +TeamGateway ..> Color + +class TeamModel{ + --- + + __construct(gateway : TeamGateway) + + createTeam(name : string,picture : string, mainColorValue : int, secondColorValue : int, errors : array) + + listByName(name : string ,errors : array) : ?array + + displayTeam(id : int): Team +} + +TeamModel *--"- gateway" TeamGateway +TeamModel ..> Team +TeamModel ..> Color + +class TeamController{ + - twig : Environement + -- + + __construct( model : TeamModel, twig : Environement) + + displaySubmitTeam() : HttpResponse + + submitTeam(request : array) : HttpResponse + + displayListTeamByName(): HttpResponse + + listTeamByName(request : array) : HttpResponse + + displayTeam(id : int): HttpResponse +} + +TeamController *--"- model" TeamModel + +class Connexion{ + - pdo : PDO + -- + + __constructor(pdo : PDO) + + exec(query : string, args : array) + + fetch(query string, args array): array +} + +@enduml \ No newline at end of file diff --git a/public/index.php b/public/index.php index edc4cb3..6633860 100644 --- a/public/index.php +++ b/public/index.php @@ -49,6 +49,15 @@ $router->map("GET", "/tactic/new", fn() => $editorController->makeNew()); $router->map("GET", "/tactic/[i:id]/edit", fn(int $id) => $editorController->openEditorFor($id)); $router->map("GET", "/tactic/[i:id]", fn(int $id) => $visualizerController->openVisualizer($id)); +$teamController = new \App\Controller\TeamController(new \App\Model\TeamModel(new \App\Gateway\TeamGateway($con))); +$router->map("GET", "/team/new", fn() => $teamController->displaySubmitTeam()); +$router->map("POST", "/team/new", fn() => $teamController->SubmitTeam($_POST)); + +$router->map("GET", "/team/list", fn() => $teamController->displayListTeamByName()); +$router->map("POST", "/team/list", fn() => $teamController->ListTeamByName($_POST)); + +$router->map("GET", "/team/[i:id]", fn(int $id) => $teamController->getTeam($id)); + $match = $router->match(); if ($match == null) { diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index 108b62a..e29b230 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -1,8 +1,10 @@ - -- drop tables here DROP TABLE IF EXISTS FormEntries; DROP TABLE IF EXISTS AccountUser; DROP TABLE IF EXISTS TacticInfo; +DROP TABLE IF EXISTS Team; +DROP TABLE IF EXISTS User; +DROP TABLE IF EXISTS Member; CREATE TABLE FormEntries(name varchar, description varchar); CREATE TABLE AccountUser( @@ -11,8 +13,28 @@ CREATE TABLE AccountUser( email varchar unique ); +CREATE TABLE Team( + id integer PRIMARY KEY AUTOINCREMENT, + name varchar, + picture varchar, + mainColor varchar, + secondColor varchar +); + +CREATE TABLE User( + id integer PRIMARY KEY AUTOINCREMENT +); + +CREATE TABLE Member( + idTeam integer, + idMember integer, + role char(1) CHECK (role IN ('C','P')), + FOREIGN KEY (idTeam) REFERENCES Team(id), + FOREIGN KEY (idMember) REFERENCES User(id) +); + CREATE TABLE TacticInfo( id integer PRIMARY KEY AUTOINCREMENT, name varchar, creation_date timestamp DEFAULT CURRENT_TIMESTAMP -); \ No newline at end of file +); diff --git a/src/Controller/TeamController.php b/src/Controller/TeamController.php new file mode 100644 index 0000000..08e22cc --- /dev/null +++ b/src/Controller/TeamController.php @@ -0,0 +1,83 @@ +model = $model; + } + + public function displaySubmitTeam(): HttpResponse { + return ViewHttpResponse::twig("insert_team.html.twig", []); + } + + /** + * @param array $request + * @return HttpResponse + */ + public function submitTeam(array $request): HttpResponse { + $errors = []; + + $request = HttpRequest::from($request, $errors, [ + "name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()], + "mainColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')], + "secondColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')], + "picture" => [Validators::isURL()], + ]); + if (!empty($errors)) { + $badFields = []; + foreach ($errors as $e) { + if ($e instanceof FieldValidationFail) { + $badFields[] = $e->getFieldName(); + } + } + return ViewHttpResponse::twig('insert_team.html.twig', ['bad_fields' => $badFields]); + } + return $this->getTeam($this->model->createTeam($request['name'], $request['picture'], $request['mainColor'], $request['secondColor'])); + } + + public function displayListTeamByName(): HttpResponse { + return ViewHttpResponse::twig("list_team_by_name.html.twig", []); + } + + /** + * @param array $request + * @return HttpResponse + */ + public function listTeamByName(array $request): HttpResponse { + $errors = []; + $request = HttpRequest::from($request, $errors, [ + "name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()], + ]); + + if (!empty($errors) && $errors[0] instanceof FieldValidationFail) { + $badField = $errors[0]->getFieldName(); + return ViewHttpResponse::twig('list_team_by_name.html.twig', ['bad_field' => $badField]); + } + + $results = $this->model->listByName($request['name']); + + if (empty($results)) { + return ViewHttpResponse::twig('display_teams.html.twig', []); + } + + return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $results]); + } + + public function getTeam(int $id): HttpResponse { + $result = $this->model->displayTeam($id); + return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]); + } +} diff --git a/src/Data/Color.php b/src/Data/Color.php index 0b1fbb3..b12e27a 100755 --- a/src/Data/Color.php +++ b/src/Data/Color.php @@ -2,29 +2,46 @@ namespace App\Data; -use http\Exception\InvalidArgumentException; +use InvalidArgumentException; class Color { /** - * @var int 6 bytes unsigned int that represents an RGB color + * @var string that represents an hexadecimal color code */ - private int $value; + private string $hex; /** - * @param int $value 6 bytes unsigned int that represents an RGB color - * @throws \InvalidArgumentException if the value is negative or greater than 0xFFFFFF + * @param string $value 6 bytes unsigned int that represents an RGB color + * @throws InvalidArgumentException if the value is negative or greater than 0xFFFFFF */ - public function __construct(int $value) { + + private function __construct(string $value) { if ($value < 0 || $value > 0xFFFFFF) { throw new InvalidArgumentException("int color value is invalid, must be positive and lower than 0xFFFFFF"); } - $this->value = $value; + $this->hex = $value; } /** - * @return int + * @return string */ - public function getValue(): int { - return $this->value; + public function getValue(): string { + return $this->hex; + } + + public static function from(string $value): Color { + $color = self::tryFrom($value); + if ($color == null) { + var_dump($value); + throw new InvalidArgumentException("The string is not an hexadecimal code"); + } + return $color; + } + + public static function tryFrom(string $value): ?Color { + if (!preg_match('/#(?:[0-9a-fA-F]{6})/', $value)) { + return null; + } + return new Color($value); } } diff --git a/src/Data/Member.php b/src/Data/Member.php index 6500733..b415f65 100755 --- a/src/Data/Member.php +++ b/src/Data/Member.php @@ -25,6 +25,7 @@ class Member { $this->role = $role; } + /** * @return int */ @@ -38,5 +39,4 @@ class Member { public function getRole(): MemberRole { return $this->role; } - } diff --git a/src/Data/MemberRole.php b/src/Data/MemberRole.php index a1d4d31..559d516 100755 --- a/src/Data/MemberRole.php +++ b/src/Data/MemberRole.php @@ -24,6 +24,14 @@ final class MemberRole { $this->value = $val; } + public static function player(): MemberRole { + return new MemberRole(MemberRole::ROLE_PLAYER); + } + + public static function coach(): MemberRole { + return new MemberRole(MemberRole::ROLE_COACH); + } + private function isValid(int $val): bool { return ($val <= self::MAX and $val >= self::MIN); } diff --git a/src/Data/Team.php b/src/Data/Team.php index aca4e0d..e50ad40 100755 --- a/src/Data/Team.php +++ b/src/Data/Team.php @@ -2,11 +2,10 @@ namespace App\Data; -use http\Url; - class Team { + private int $id; private string $name; - private Url $picture; + private string $picture; private Color $mainColor; private Color $secondColor; @@ -17,12 +16,13 @@ class Team { /** * @param string $name - * @param Url $picture + * @param string $picture * @param Color $mainColor * @param Color $secondColor * @param Member[] $members */ - public function __construct(string $name, Url $picture, Color $mainColor, Color $secondColor, array $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; @@ -30,6 +30,13 @@ class Team { $this->members = $members; } + /** + * @return int + */ + public function getId(): int { + return $this->id; + } + /** * @return string */ @@ -38,9 +45,9 @@ class Team { } /** - * @return Url + * @return string */ - public function getPicture(): Url { + public function getPicture(): string { return $this->picture; } diff --git a/src/Gateway/TeamGateway.php b/src/Gateway/TeamGateway.php new file mode 100644 index 0000000..c3d22dc --- /dev/null +++ b/src/Gateway/TeamGateway.php @@ -0,0 +1,79 @@ +con = $con; + } + + public function insert(string $name, string $picture, string $mainColor, string $secondColor): void { + $this->con->exec( + "INSERT INTO Team(name, picture, mainColor, secondColor) VALUES (:teamName , :picture, :mainColor, :secondColor)", + [ + ":teamName" => [$name, PDO::PARAM_STR], + ":picture" => [$picture, PDO::PARAM_STR], + ":mainColor" => [$mainColor, PDO::PARAM_STR], + ":secondColor" => [$secondColor, PDO::PARAM_STR], + ] + ); + } + + /** + * @param string $name + * @return array[] + */ + public function listByName(string $name): array { + return $this->con->fetch( + "SELECT id,name,picture,mainColor,secondColor FROM Team WHERE name LIKE '%' || :name || '%'", + [ + ":name" => [$name, PDO::PARAM_STR], + ] + ); + } + + /** + * @param int $id + * @return array[] + */ + public function getTeamById(int $id): array { + return $this->con->fetch( + "SELECT id,name,picture,mainColor,secondColor FROM Team WHERE id = :id", + [ + ":id" => [$id, PDO::PARAM_INT], + ] + ); + } + + /** + * @param string $name + * @return array[] + */ + public function getIdTeamByName(string $name): array { + return $this->con->fetch( + "SELECT id FROM Team WHERE name = :name", + [ + ":name" => [$name, PDO::PARAM_STR], + ] + ); + } + + /** + * @param int $id + * @return array[] + */ + public function getMembersById(int $id): array { + return $this->con->fetch( + "SELECT m.role,u.id FROM User u,Team t,Member m WHERE t.id = :id AND m.idTeam = t.id AND m.idMember = u.id", + [ + ":id" => [$id, PDO::PARAM_INT], + ] + ); + } + +} diff --git a/src/Model/TeamModel.php b/src/Model/TeamModel.php new file mode 100644 index 0000000..65f22a7 --- /dev/null +++ b/src/Model/TeamModel.php @@ -0,0 +1,54 @@ +gateway = $gateway; + } + + public function createTeam(string $name, string $picture, string $mainColor, string $secondColor): int { + $this->gateway->insert($name, $picture, $mainColor, $secondColor); + $result = $this->gateway->getIdTeamByName($name); + return intval($result[0]['id']); + } + + /** + * @param string $name + * @return Team[] + */ + public function listByName(string $name): array { + $teams = []; + $results = $this->gateway->listByName($name); + foreach ($results as $row) { + $teams[] = new Team($row['id'], $row['name'], $row['picture'], Color::from($row['mainColor']), Color::from($row['secondColor'])); + } + return $teams; + } + + public function displayTeam(int $id): Team { + $members = []; + $result = $this->gateway->getTeamById($id)[0]; + $resultMembers = $this->gateway->getMembersById($id); + foreach ($resultMembers as $row) { + if ($row['role'] == 'C') { + $role = MemberRole::coach(); + } else { + $role = MemberRole::player(); + } + $members[] = new Member($row['id'], $role); + } + return new Team(intval($result['id']), $result['name'], $result['picture'], Color::from($result['mainColor']), Color::from($result['secondColor']), $members); + } +} diff --git a/src/Validation/Validators.php b/src/Validation/Validators.php index 94aea23..cb28007 100644 --- a/src/Validation/Validators.php +++ b/src/Validation/Validators.php @@ -50,4 +50,23 @@ class Validators { } ); } + + + public static function isInteger(): Validator { + return self::regex("/^[0-9]+$/"); + } + + public static function isIntInRange(int $min, int $max): Validator { + return new SimpleFunctionValidator( + fn(string $val) => intval($val) >= $min && intval($val) <= $max, + fn(string $name) => [new FieldValidationFail($name, "The value is not in the range $min to $max ")] + ); + } + + public static function isURL(): Validator { + return new SimpleFunctionValidator( + fn($val) => filter_var($val, FILTER_VALIDATE_URL), + fn(string $name) => [new FieldValidationFail($name, "The value is not an URL")] + ); + } } diff --git a/src/Views/display_team.html.twig b/src/Views/display_team.html.twig new file mode 100644 index 0000000..ada8566 --- /dev/null +++ b/src/Views/display_team.html.twig @@ -0,0 +1,82 @@ + + + + + Twig view + + + +
+

IQBall

+
+ +
+ +
+
+

{{ team.name }}

+ +
+
+

Couleur principale :

+

Couleur secondaire :

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

m.id

+ {% endfor %} +
+ +
+ + \ No newline at end of file diff --git a/src/Views/display_teams.html.twig b/src/Views/display_teams.html.twig new file mode 100644 index 0000000..c0ac185 --- /dev/null +++ b/src/Views/display_teams.html.twig @@ -0,0 +1,33 @@ + + + + + Twig view + + + +{% if teams is empty %} +

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

+
+

Chercher une équipe

+
+
+ + +
+
+ +
+
+
+{% else %} + {% for t in teams %} +
+

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

+ logo de l'équipe +
+ {% endfor %} +{% endif %} + + + \ No newline at end of file diff --git a/src/Views/insert_team.html.twig b/src/Views/insert_team.html.twig new file mode 100644 index 0000000..0cc85dd --- /dev/null +++ b/src/Views/insert_team.html.twig @@ -0,0 +1,85 @@ + + + + + Insertion view + + + + +
+

Créer une équipe

+
+
+ + + + + + + + +
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/src/Views/list_team_by_name.html.twig b/src/Views/list_team_by_name.html.twig new file mode 100644 index 0000000..1d9ddf3 --- /dev/null +++ b/src/Views/list_team_by_name.html.twig @@ -0,0 +1,77 @@ + + + + + Insertion view + + + + +
+

Chercher une équipe

+
+
+ + +
+
+ +
+
+
+ + + \ No newline at end of file