add /team/new and /team/[i:id] and /team/list #16

Merged
mael.daim merged 18 commits from team/bootstrap into master 1 year ago

2
.gitignore vendored

@ -4,7 +4,7 @@
.vite .vite
vendor vendor
.nfs*
composer.lock composer.lock
*.phar *.phar
/dist /dist

@ -0,0 +1,68 @@
@startuml
class Team {
- name: String
- picture: Url
- members: array<int, MemberRole>
+ getName(): String
+ getPicture(): Url
+ getMainColor(): Color
+ getSecondColor(): Color
+ listMembers(): array<Member>
}
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

@ -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]/edit", fn(int $id) => $editorController->openEditorFor($id));
$router->map("GET", "/tactic/[i:id]", fn(int $id) => $visualizerController->openVisualizer($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(); $match = $router->match();
if ($match == null) { if ($match == null) {

@ -1,8 +1,10 @@
-- drop tables here -- drop tables here
DROP TABLE IF EXISTS FormEntries; DROP TABLE IF EXISTS FormEntries;
DROP TABLE IF EXISTS AccountUser; DROP TABLE IF EXISTS AccountUser;
DROP TABLE IF EXISTS TacticInfo; 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 FormEntries(name varchar, description varchar);
CREATE TABLE AccountUser( CREATE TABLE AccountUser(
@ -11,6 +13,26 @@ CREATE TABLE AccountUser(
email varchar unique 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( CREATE TABLE TacticInfo(
id integer PRIMARY KEY AUTOINCREMENT, id integer PRIMARY KEY AUTOINCREMENT,
name varchar, name varchar,

@ -0,0 +1,83 @@
<?php
namespace App\Controller;
use App\Http\HttpRequest;
use App\Http\HttpResponse;
use App\Http\ViewHttpResponse;
use App\Model\TeamModel;
use App\Validation\FieldValidationFail;
use App\Validation\Validators;
class TeamController {
private TeamModel $model;
/**
* @param TeamModel $model
Review

useless, remove this

useless, remove this
*/
public function __construct(TeamModel $model) {
$this->model = $model;
}
public function displaySubmitTeam(): HttpResponse {
return ViewHttpResponse::twig("insert_team.html.twig", []);
}
/**
* @param array<string, mixed> $request
* @return HttpResponse
*/
public function submitTeam(array $request): HttpResponse {
$errors = [];
$request = HttpRequest::from($request, $errors, [
"name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()],
"mainColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')],
"secondColor" => [Validators::regex('/#(?:[0-9a-fA-F]{6})/')],
"picture" => [Validators::isURL()],
]);
if (!empty($errors)) {
$badFields = [];
foreach ($errors as $e) {
if ($e instanceof FieldValidationFail) {
$badFields[] = $e->getFieldName();
}
}
return ViewHttpResponse::twig('insert_team.html.twig', ['bad_fields' => $badFields]);
}
return $this->getTeam($this->model->createTeam($request['name'], $request['picture'], $request['mainColor'], $request['secondColor']));
}
public function displayListTeamByName(): HttpResponse {
return ViewHttpResponse::twig("list_team_by_name.html.twig", []);
}
/**
* @param array<string , mixed> $request
* @return HttpResponse
*/
public function listTeamByName(array $request): HttpResponse {
$errors = [];
$request = HttpRequest::from($request, $errors, [
"name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()],
]);
if (!empty($errors) && $errors[0] instanceof FieldValidationFail) {
$badField = $errors[0]->getFieldName();
return ViewHttpResponse::twig('list_team_by_name.html.twig', ['bad_field' => $badField]);
}
$results = $this->model->listByName($request['name']);
if (empty($results)) {
return ViewHttpResponse::twig('display_teams.html.twig', []);
}
return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $results]);
}
public function getTeam(int $id): HttpResponse {
$result = $this->model->displayTeam($id);
return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]);
}
}

@ -2,29 +2,46 @@
namespace App\Data; namespace App\Data;
use http\Exception\InvalidArgumentException; use InvalidArgumentException;
class Color { 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 * @param string $value 6 bytes unsigned int that represents an RGB color
* @throws \InvalidArgumentException if the value is negative or greater than 0xFFFFFF * @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) { if ($value < 0 || $value > 0xFFFFFF) {
throw new InvalidArgumentException("int color value is invalid, must be positive and lower than 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 { public function getValue(): string {
return $this->value; 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);
} }
} }

@ -25,6 +25,7 @@ class Member {
$this->role = $role; $this->role = $role;
} }
/** /**
* @return int * @return int
*/ */
@ -38,5 +39,4 @@ class Member {
public function getRole(): MemberRole { public function getRole(): MemberRole {
return $this->role; return $this->role;
} }
} }

@ -24,6 +24,14 @@ final class MemberRole {
$this->value = $val; $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 { private function isValid(int $val): bool {
return ($val <= self::MAX and $val >= self::MIN); return ($val <= self::MAX and $val >= self::MIN);
} }

@ -2,11 +2,10 @@
namespace App\Data; namespace App\Data;
use http\Url;
class Team { class Team {
private int $id;
private string $name; private string $name;
private Url $picture; private string $picture;
private Color $mainColor; private Color $mainColor;
private Color $secondColor; private Color $secondColor;
@ -17,12 +16,13 @@ class Team {
/** /**
* @param string $name * @param string $name
* @param Url $picture * @param string $picture
* @param Color $mainColor * @param Color $mainColor
* @param Color $secondColor * @param Color $secondColor
* @param Member[] $members * @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->name = $name;
$this->picture = $picture; $this->picture = $picture;
$this->mainColor = $mainColor; $this->mainColor = $mainColor;
@ -30,6 +30,13 @@ class Team {
$this->members = $members; $this->members = $members;
} }
/**
* @return int
*/
public function getId(): int {
return $this->id;
}
/** /**
* @return string * @return string
*/ */
@ -38,9 +45,9 @@ class Team {
} }
/** /**
* @return Url * @return string
*/ */
public function getPicture(): Url { public function getPicture(): string {
return $this->picture; return $this->picture;
} }

@ -0,0 +1,79 @@
<?php
namespace App\Gateway;
use App\Connexion;
use PDO;
class TeamGateway {
private Connexion $con;
public function __construct(Connexion $con) {
$this->con = $con;
}
public function insert(string $name, string $picture, string $mainColor, string $secondColor): void {
$this->con->exec(
"INSERT INTO Team(name, picture, mainColor, secondColor) VALUES (:teamName , :picture, :mainColor, :secondColor)",
[
":teamName" => [$name, PDO::PARAM_STR],
":picture" => [$picture, PDO::PARAM_STR],
":mainColor" => [$mainColor, PDO::PARAM_STR],
":secondColor" => [$secondColor, PDO::PARAM_STR],
]
);
}
/**
* @param string $name
* @return array<string,mixed>[]
*/
public function listByName(string $name): array {
return $this->con->fetch(
"SELECT id,name,picture,mainColor,secondColor FROM Team WHERE name LIKE '%' || :name || '%'",
[
":name" => [$name, PDO::PARAM_STR],
]
);
}
/**
* @param int $id
* @return array<string,mixed>[]
*/
public function getTeamById(int $id): array {
return $this->con->fetch(
"SELECT id,name,picture,mainColor,secondColor FROM Team WHERE id = :id",
[
":id" => [$id, PDO::PARAM_INT],
]
);
}
/**
* @param string $name
* @return array<string,int>[]
*/
public function getIdTeamByName(string $name): array {
return $this->con->fetch(
"SELECT id FROM Team WHERE name = :name",
[
":name" => [$name, PDO::PARAM_STR],
]
);
}
/**
* @param int $id
* @return array<string,mixed>[]
*/
public function getMembersById(int $id): array {
return $this->con->fetch(
"SELECT m.role,u.id FROM User u,Team t,Member m WHERE t.id = :id AND m.idTeam = t.id AND m.idMember = u.id",
[
":id" => [$id, PDO::PARAM_INT],
]
);
}
}

@ -0,0 +1,54 @@
<?php
namespace App\Model;
use App\Gateway\TeamGateway;
use App\Data\Team;
use App\Data\Member;
use App\Data\MemberRole;
use App\Data\Color;
class TeamModel {
private TeamGateway $gateway;
/**
* @param TeamGateway $gateway
*/
public function __construct(TeamGateway $gateway) {
$this->gateway = $gateway;
}
public function createTeam(string $name, string $picture, string $mainColor, string $secondColor): int {
$this->gateway->insert($name, $picture, $mainColor, $secondColor);
$result = $this->gateway->getIdTeamByName($name);
return intval($result[0]['id']);
}
/**
* @param string $name
* @return Team[]
*/
public function listByName(string $name): array {
$teams = [];
$results = $this->gateway->listByName($name);
foreach ($results as $row) {
$teams[] = new Team($row['id'], $row['name'], $row['picture'], Color::from($row['mainColor']), Color::from($row['secondColor']));
}
return $teams;
}
public function displayTeam(int $id): Team {
$members = [];
$result = $this->gateway->getTeamById($id)[0];
$resultMembers = $this->gateway->getMembersById($id);
foreach ($resultMembers as $row) {
if ($row['role'] == 'C') {
$role = MemberRole::coach();
} else {
$role = MemberRole::player();
}
$members[] = new Member($row['id'], $role);
}
return new Team(intval($result['id']), $result['name'], $result['picture'], Color::from($result['mainColor']), Color::from($result['secondColor']), $members);
}
}

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

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Twig view</title>
<style>
body{
background-color: #f1f1f1;
display: flex;
flex-direction: column;
align-items: center;
}
section{
width: 60%;
}
.square{
width:50px;
height:50px;
}
#mainColor{
background-color: {{ team.mainColor.getValue() }};
{% if team.mainColor.getValue() == "#ffffff" %}
border-color: #666666;
{% endif %}
}
#secondColor{
background-color: {{ team.secondColor.getValue() }};
{% if team.secondColor.getValue() == "#ffffff" %}
border-color: #666666;
{% endif %}
}
.container{
background-color: #fff;
display: flex;
flex-direction: column;
align-items: center;
}
.team{
border-color: darkgrey;
border-radius: 20px;
}
.color{
flex-direction: row;
justify-content: space-between;
}
.logo{
height: 80px;
width: 80px;
}
</style>
</head>
<body>
<header>
<h1><a href="/">IQBall</a></h1>
</header>
<section class="container">
<div class="team container">
<div>
<h1>{{ team.name }}</h1>
<img src="{{ team.picture }}" 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>
{% for m in team.members %}
<p> m.id </p>
{% endfor %}
</div>
</section>
</body>
</html>

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Twig view</title>
</head>
<body>
{% if teams is empty %}
<p>Aucune équipe n'a été trouvée</p>
<div class="container">
<h2>Chercher une équipe</h2>
<form action="/team/list" method="post">
<div class="form-group">
<label for="name">Nom de l'équipe :</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<input type="submit" value="Confirmer">
</div>
</form>
</div>
{% else %}
{% for t in teams %}
<div class="team" onclick="window.location.href = '/team/{{ t.id }}'">
<p>Nom de l'équipe : {{ t.name }}</p>
<img src="{{ t.picture }}" alt="logo de l'équipe">
</div>
{% endfor %}
{% endif %}
</body>
</html>

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Insertion view</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f1f1f1;
}
.container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
}
{% for item in bad_fields %}
#{{ item }}{
border-color: red;
}
{% endfor %}
input[type="text"], input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
input[type="submit"] {
background-color: #007bff;
color: #fff;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="container">
<h2>Créer une équipe</h2>
<form action="/team/new" method="post">
<div class="form-group">
<label for="name">Nom de l'équipe :</label>
<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>
</div>
<div class="form-group">
<input type="submit" value="Confirmer">
</div>
</form>
</div>
</body>
</html>

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Insertion view</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f1f1f1;
}
.container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
}
{% for item in bad_fields %}
#{{ item }}{
border-color: red;
}
{% endfor %}
input[type="text"], input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
input[type="submit"] {
background-color: #007bff;
color: #fff;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="container">
<h2>Chercher une équipe</h2>
<form action="/team/list" method="post">
<div class="form-group">
<label for="name">Nom de l'équipe :</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<input type="submit" value="Confirmer">
</div>
</form>
</div>
</body>
</html>
Loading…
Cancel
Save