From 3a437a7ad186bb5430d17631f6e24bba63de76a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20DAIM?= Date: Tue, 9 Jan 2024 00:36:33 +0100 Subject: [PATCH] Team's part (#84) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request adds the members to the team with the possibilitie to add or remove a player or quit the team. It means that this PR implements the account within the team's part of the application. Moreover some bugs has been fixed. The view of the team has been translated to react.js This PR also adds the edit and delete of a team. Co-authored-by: mael.daim Co-authored-by: maxime Reviewed-on: https://codefirst.iut.uca.fr/git/IQBall/Application-Web/pulls/84 Co-authored-by: Maël DAIM Co-committed-by: Maël DAIM --- front/components/editor/BasketCourt.tsx | 9 +- front/components/editor/CourtBall.tsx | 2 +- front/components/editor/CourtPlayer.tsx | 2 +- front/components/editor/PlayerPiece.tsx | 4 +- front/model/Team.ts | 19 ++ front/model/User.ts | 6 + front/{ => model}/tactic/Action.ts | 4 +- .../CourtObjects.ts => model/tactic/Ball.ts} | 0 front/{ => model}/tactic/Player.ts | 9 +- front/{ => model}/tactic/Tactic.ts | 2 +- front/style/team_panel.css | 135 ++++++++++++++ front/tactic/Team.ts | 4 - front/views/Editor.tsx | 26 +-- front/views/Home.tsx | 2 +- front/views/TeamPanel.tsx | 169 ++++++++++++++++++ front/views/editor/CourtAction.tsx | 2 +- public/api/index.php | 1 - public/index.php | 11 +- sql/setup-tables.sql | 3 +- src/Api/Controller/APITacticController.php | 2 +- src/App/Controller/EditorController.php | 4 +- src/App/Controller/TeamController.php | 151 ++++++++++++---- src/App/Controller/UserController.php | 18 +- src/App/Controller/VisualizerController.php | 2 +- src/App/Views/add_member.html.twig | 29 ++- src/App/Views/display_team.html.twig | 56 ++++-- src/App/Views/display_teams.html.twig | 38 +++- src/App/Views/edit_team.html.twig | 81 +++++++++ src/App/Views/home.twig | 4 +- src/App/Views/insert_team.html.twig | 3 +- src/App/Views/list_team_by_name.html.twig | 7 +- src/Core/Data/Account.php | 40 ++--- src/Core/Data/Color.php | 44 ----- src/Core/Data/Member.php | 44 ++--- src/Core/Data/MemberRole.php | 68 ------- src/Core/Data/Team.php | 8 +- src/Core/Data/TeamInfo.php | 20 ++- src/Core/Data/User.php | 72 ++++++++ src/Core/Gateway/AccountGateway.php | 11 +- src/Core/Gateway/MemberGateway.php | 39 +++- src/Core/Gateway/TeamGateway.php | 76 ++++++-- src/Core/Model/AuthModel.php | 11 +- src/Core/Model/TeamModel.php | 89 +++++++-- 43 files changed, 988 insertions(+), 339 deletions(-) create mode 100644 front/model/Team.ts create mode 100644 front/model/User.ts rename front/{ => model}/tactic/Action.ts (75%) rename front/{tactic/CourtObjects.ts => model/tactic/Ball.ts} (100%) rename front/{ => model}/tactic/Player.ts (84%) rename front/{ => model}/tactic/Tactic.ts (85%) create mode 100644 front/style/team_panel.css delete mode 100644 front/tactic/Team.ts create mode 100644 front/views/TeamPanel.tsx create mode 100644 src/App/Views/edit_team.html.twig delete mode 100755 src/Core/Data/Color.php delete mode 100755 src/Core/Data/MemberRole.php create mode 100644 src/Core/Data/User.php diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index e59bdb7..1e3d916 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -9,12 +9,13 @@ import { } from "react" import CourtPlayer from "./CourtPlayer" -import { Player } from "../../tactic/Player" -import { Action, ActionKind } from "../../tactic/Action" + +import { Player } from "../../model/tactic/Player" +import { Action, ActionKind } from "../../model/tactic/Action" import ArrowAction from "../actions/ArrowAction" import { middlePos, ratioWithinBase } from "../arrows/Pos" import BallAction from "../actions/BallAction" -import { CourtObject } from "../../tactic/CourtObjects" +import { CourtObject } from "../../model/tactic/Ball" import { contains } from "../arrows/Box" import { CourtAction } from "../../views/editor/CourtAction" @@ -255,7 +256,7 @@ export function BasketCourt({ /> ) } - throw new Error("unknown court object", object.type) + throw new Error("unknown court object" + object.type) })} {previewAction && ( diff --git a/front/components/editor/CourtBall.tsx b/front/components/editor/CourtBall.tsx index 9ba5ae5..b1fa1d0 100644 --- a/front/components/editor/CourtBall.tsx +++ b/front/components/editor/CourtBall.tsx @@ -1,7 +1,7 @@ import React, { useRef } from "react" import Draggable from "react-draggable" import { BallPiece } from "./BallPiece" -import { Ball } from "../../tactic/CourtObjects" +import { Ball } from "../../model/tactic/Ball" export interface CourtBallProps { onMoved: (rect: DOMRect) => void diff --git a/front/components/editor/CourtPlayer.tsx b/front/components/editor/CourtPlayer.tsx index 3d5ffde..c0c94d5 100644 --- a/front/components/editor/CourtPlayer.tsx +++ b/front/components/editor/CourtPlayer.tsx @@ -2,7 +2,7 @@ import { ReactNode, RefObject, useRef } from "react" import "../../style/player.css" import Draggable from "react-draggable" import { PlayerPiece } from "./PlayerPiece" -import { Player } from "../../tactic/Player" +import { Player } from "../../model/tactic/Player" import { NULL_POS, ratioWithinBase } from "../arrows/Pos" export interface PlayerProps { diff --git a/front/components/editor/PlayerPiece.tsx b/front/components/editor/PlayerPiece.tsx index a1b5e74..1b52ff8 100644 --- a/front/components/editor/PlayerPiece.tsx +++ b/front/components/editor/PlayerPiece.tsx @@ -1,8 +1,8 @@ import "../../style/player.css" -import { Team } from "../../tactic/Team" +import { PlayerTeam } from "../../model/tactic/Player" export interface PlayerPieceProps { - team: Team + team: PlayerTeam text: string hasBall: boolean } diff --git a/front/model/Team.ts b/front/model/Team.ts new file mode 100644 index 0000000..99d530b --- /dev/null +++ b/front/model/Team.ts @@ -0,0 +1,19 @@ +import {User} from "./User"; + +export interface TeamInfo { + id: number + name: string + picture: string + mainColor: string + secondColor: string +} + +export interface Team { + info: TeamInfo + members: Member[] +} + +export interface Member { + user: User + role: string +} diff --git a/front/model/User.ts b/front/model/User.ts new file mode 100644 index 0000000..36bbb67 --- /dev/null +++ b/front/model/User.ts @@ -0,0 +1,6 @@ +export interface User { + id: number + name: string + email: string + profilePicture: string +} diff --git a/front/tactic/Action.ts b/front/model/tactic/Action.ts similarity index 75% rename from front/tactic/Action.ts rename to front/model/tactic/Action.ts index d66f375..0b5aee5 100644 --- a/front/tactic/Action.ts +++ b/front/model/tactic/Action.ts @@ -1,5 +1,5 @@ -import { Pos } from "../components/arrows/Pos" -import { Segment } from "../components/arrows/BendableArrow" +import { Pos } from "../../components/arrows/Pos" +import { Segment } from "../../components/arrows/BendableArrow" import { PlayerId } from "./Player" export enum ActionKind { diff --git a/front/tactic/CourtObjects.ts b/front/model/tactic/Ball.ts similarity index 100% rename from front/tactic/CourtObjects.ts rename to front/model/tactic/Ball.ts diff --git a/front/tactic/Player.ts b/front/model/tactic/Player.ts similarity index 84% rename from front/tactic/Player.ts rename to front/model/tactic/Player.ts index 1d71d8a..f94d6bf 100644 --- a/front/tactic/Player.ts +++ b/front/model/tactic/Player.ts @@ -1,14 +1,17 @@ -import { Team } from "./Team" - export type PlayerId = string +export enum PlayerTeam { + Allies = "allies", + Opponents = "opponents", +} + export interface Player { readonly id: PlayerId /** * the player's team * */ - readonly team: Team + readonly team: PlayerTeam /** * player's role diff --git a/front/tactic/Tactic.ts b/front/model/tactic/Tactic.ts similarity index 85% rename from front/tactic/Tactic.ts rename to front/model/tactic/Tactic.ts index 296c339..2eab85b 100644 --- a/front/tactic/Tactic.ts +++ b/front/model/tactic/Tactic.ts @@ -1,5 +1,5 @@ import { Player } from "./Player" -import { CourtObject } from "./CourtObjects" +import { CourtObject } from "./Ball" import { Action } from "./Action" export interface Tactic { diff --git a/front/style/team_panel.css b/front/style/team_panel.css new file mode 100644 index 0000000..6e48795 --- /dev/null +++ b/front/style/team_panel.css @@ -0,0 +1,135 @@ +#main-div { + display: flex; + flex-direction: column; + align-items: center; + height: 100%; +} + +#main-div header { + display: flex; + justify-content: center; + background-color: #525252; + width: 100%; + margin-bottom: 5px; +} + +header h1 a { + color: orange; + text-decoration: none; + font-size: 1.5em; +} + +.square { + width: 50px; + height: 50px; + border: 2px white solid; +} + +#team-info { + display: flex; + flex-direction: column; + align-items: center; + width: 60%; + background-color: #8f8f8f; + padding-bottom: 10px; + border-radius: 10px; +} + +#first-part { + display: flex; + flex-direction: column; + align-items: center; +} + +#team-name { + font-size: 2.8em; +} + +#colors { + display: flex; + flex-direction: column; +} +.color { + flex-direction: row; + justify-content: space-between; +} + +#colorsTitle { + width: 110%; + display: flex; + flex-direction: row; + justify-content: space-between; + font-size: 1.3em; + color: white; +} + +#actual-colors { + display: flex; + flex-direction: row; + justify-content: space-around; +} + +#logo { + aspect-ratio: 3/2; + object-fit: contain; + max-width: 70%; + max-height: 70%; +} + +#delete { + border-radius: 10px; + background-color: red; + color: white; + margin-top: 10px; + margin-bottom: 10px; + margin-right: 5px; +} + +#edit { + border-radius: 10px; + background-color: orange; + color: white; + margin-top: 10px; + margin-bottom: 10px; +} + +#head-members { + width: 33%; + display: flex; + flex-direction: row; + justify-content: space-evenly; +} + +#add-member { + height: 30px; + aspect-ratio: 1/1; + border-radius: 100%; + align-self: center; +} + +#members { + display: flex; + flex-direction: column; + background-color: #bcbcbc; + width: 60%; + align-items: center; + justify-content: space-around; + border-radius: 10px; +} + +.member { + width: 60%; + background-color: white; + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: center; + border-radius: 10px; + margin-top: 5px; + margin-bottom: 5px; +} + +#profile-picture { + height: 40px; + width: 40px; +} diff --git a/front/tactic/Team.ts b/front/tactic/Team.ts deleted file mode 100644 index 5b35943..0000000 --- a/front/tactic/Team.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum Team { - Allies = "allies", - Opponents = "opponents", -} diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 69c1eca..e1f0edd 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -16,22 +16,22 @@ import { BallPiece } from "../components/editor/BallPiece" import { Rack } from "../components/Rack" import { PlayerPiece } from "../components/editor/PlayerPiece" -import { Player } from "../tactic/Player" +import { Player } from "../model/tactic/Player" -import { Tactic, TacticContent } from "../tactic/Tactic" +import { Tactic, TacticContent } from "../model/tactic/Tactic" import { fetchAPI } from "../Fetcher" -import { Team } from "../tactic/Team" +import { PlayerTeam } from "../model/tactic/Player" import SavingState, { SaveState, SaveStates, } from "../components/editor/SavingState" -import { CourtObject } from "../tactic/CourtObjects" +import { CourtObject } from "../model/tactic/Ball" import { CourtAction } from "./editor/CourtAction" import { BasketCourt } from "../components/editor/BasketCourt" import { ratioWithinBase } from "../components/arrows/Pos" -import { Action, ActionKind } from "../tactic/Action" +import { Action, ActionKind } from "../model/tactic/Action" const ERROR_STYLE: CSSProperties = { borderColor: "red", @@ -58,7 +58,7 @@ export interface EditorProps { * information about a player that is into a rack */ interface RackedPlayer { - team: Team + team: PlayerTeam key: string } @@ -134,10 +134,10 @@ function EditorView({ ) const [allies, setAllies] = useState( - getRackPlayers(Team.Allies, content.players), + getRackPlayers(PlayerTeam.Allies, content.players), ) const [opponents, setOpponents] = useState( - getRackPlayers(Team.Opponents, content.players), + getRackPlayers(PlayerTeam.Opponents, content.players), ) const [objects, setObjects] = useState( @@ -222,7 +222,7 @@ function EditorView({ break default: - throw new Error("unknown court object ", rackedObject.key) + throw new Error("unknown court object " + rackedObject.key) } setContent((content) => { @@ -357,10 +357,10 @@ function EditorView({ })) let setter switch (player.team) { - case Team.Opponents: + case PlayerTeam.Opponents: setter = setOpponents break - case Team.Allies: + case PlayerTeam.Allies: setter = setAllies } if (player.hasBall) { @@ -539,7 +539,7 @@ function renderCourtObject(courtObject: RackedCourtObject) { if (courtObject.key == "ball") { return } - throw new Error("unknown racked court object ", courtObject.key) + throw new Error("unknown racked court object " + courtObject.key) } function Court({ courtType }: { courtType: string }) { @@ -554,7 +554,7 @@ function Court({ courtType }: { courtType: string }) { ) } -function getRackPlayers(team: Team, players: Player[]): RackedPlayer[] { +function getRackPlayers(team: PlayerTeam, players: Player[]): RackedPlayer[] { return ["1", "2", "3", "4", "5"] .filter( (role) => diff --git a/front/views/Home.tsx b/front/views/Home.tsx index 44803a3..8be885d 100644 --- a/front/views/Home.tsx +++ b/front/views/Home.tsx @@ -162,7 +162,7 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) { function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) { let data if (allTactics.length == 0) { - data =

Aucune tactique créé !

+ data =

Aucune tactique créée !

} else { data = } diff --git a/front/views/TeamPanel.tsx b/front/views/TeamPanel.tsx new file mode 100644 index 0000000..709d7f2 --- /dev/null +++ b/front/views/TeamPanel.tsx @@ -0,0 +1,169 @@ +import "../style/team_panel.css" +import { BASE } from "../Constants" +import { Team, TeamInfo, Member } from "../model/Team" +import { User } from "../model/User" + +export default function TeamPanel({ + isCoach, + team, + currentUserId, +}: { + isCoach: boolean + team: Team + currentUserId: number +}) { + return ( +
+
+

+ IQBall +

+
+ + + {isCoach && } + + +
+ ) +} + +function TeamDisplay({ team }: { team: TeamInfo }) { + return ( +
+
+

{team.name}

+ +
+
+
+

Couleur principale

+

Couleur secondaire

+
+
+ + +
+
+
+ ) +} + +function ColorDisplay({ color }: { color: string }) { + return
+} + +function CoachOptions({ id }: { id: number }) { + return ( +
+ + +
+ ) +} + +function MembersDisplay({ + members, + isCoach, + idTeam, + currentUserId, +}: { + members: Member[] + isCoach: boolean + idTeam: number + currentUserId: number +}) { + const listMember = members.map((member) => ( + + )) + return ( +
+
+

Membres :

+ {isCoach && ( + + )} +
+ {listMember} +
+ ) +} + +function MemberDisplay({ + member, + isCoach, + idTeam, + currentUserId, +}: { + member: Member + isCoach: boolean + idTeam: number + currentUserId: number +}) { + return ( +
+ Photo de profile +

{member.user.name}

+

{member.role}

+

{member.user.email}

+ {isCoach && currentUserId !== member.user.id && ( + + )} + {isCoach && currentUserId == member.user.id && ( + + )} +
+ ) +} diff --git a/front/views/editor/CourtAction.tsx b/front/views/editor/CourtAction.tsx index b4028ff..de33224 100644 --- a/front/views/editor/CourtAction.tsx +++ b/front/views/editor/CourtAction.tsx @@ -1,4 +1,4 @@ -import { Action, ActionKind } from "../../tactic/Action" +import { Action, ActionKind } from "../../model/tactic/Action" import BendableArrow from "../../components/arrows/BendableArrow" import { RefObject } from "react" import { MoveToHead, ScreenHead } from "../../components/actions/ArrowAction" diff --git a/public/api/index.php b/public/api/index.php index 5734571..da25013 100644 --- a/public/api/index.php +++ b/public/api/index.php @@ -51,7 +51,6 @@ function tryGetAuthorization(): ?Account { $session = PhpSessionHandle::init(); return $session->getAccount(); } - $token = $headers['Authorization']; $gateway = new AccountGateway(new Connection(get_database())); return $gateway->getAccountFromToken($token); diff --git a/public/index.php b/public/index.php index 935e2f2..82dd37f 100644 --- a/public/index.php +++ b/public/index.php @@ -102,10 +102,13 @@ function getRoutes(): AltoRouter { $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))); + $ar->map("GET", "/team/[i:id]/delete", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->deleteTeamById($id, $s))); + $ar->map("GET", "/team/[i:id]/addMember", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->displayAddMember($id, $s))); + $ar->map("POST", "/team/[i:id]/addMember", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->addMember($id, $_POST, $s))); + $ar->map("GET", "/team/[i:idTeam]/remove/[i:idMember]", Action::auth(fn(int $idTeam, int $idMember, SessionHandle $s) => getTeamController()->deleteMember($idTeam, $idMember, $s))); + $ar->map("GET", "/team/[i:id]/edit", Action::auth(fn(int $idTeam, SessionHandle $s) => getTeamController()->displayEditTeam($idTeam, $s))); + $ar->map("POST", "/team/[i:id]/edit", Action::auth(fn(int $idTeam, SessionHandle $s) => getTeamController()->editTeam($idTeam, $_POST, $s))); + return $ar; } diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index 633081f..0d157d9 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -10,7 +10,8 @@ CREATE TABLE Account email varchar UNIQUE NOT NULL, username varchar NOT NULL, token varchar UNIQUE NOT NULL, - hash varchar NOT NULL + hash varchar NOT NULL, + profilePicture varchar NOT NULL ); CREATE TABLE Tactic diff --git a/src/Api/Controller/APITacticController.php b/src/Api/Controller/APITacticController.php index 79e766c..a116add 100644 --- a/src/Api/Controller/APITacticController.php +++ b/src/Api/Controller/APITacticController.php @@ -36,7 +36,7 @@ class APITacticController { "name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()], ], function (HttpRequest $request) use ($tactic_id, $account) { - $failures = $this->model->updateName($tactic_id, $request["name"], $account->getId()); + $failures = $this->model->updateName($tactic_id, $request["name"], $account->getUser()->getId()); if (!empty($failures)) { //TODO find a system to handle Unauthorized error codes more easily from failures. diff --git a/src/App/Controller/EditorController.php b/src/App/Controller/EditorController.php index 5561590..4bdcfae 100644 --- a/src/App/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -63,7 +63,7 @@ class EditorController { return $this->openTestEditor($type); } - $tactic = $this->model->makeNewDefault($session->getAccount()->getId(), $type); + $tactic = $this->model->makeNewDefault($session->getAccount()->getUser()->getId(), $type); return $this->openEditorFor($tactic); } @@ -76,7 +76,7 @@ class EditorController { public function openEditor(int $id, SessionHandle $session): ViewHttpResponse { $tactic = $this->model->get($id); - $failure = TacticValidator::validateAccess($id, $tactic, $session->getAccount()->getId()); + $failure = TacticValidator::validateAccess($id, $tactic, $session->getAccount()->getUser()->getId()); if ($failure != null) { return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); diff --git a/src/App/Controller/TeamController.php b/src/App/Controller/TeamController.php index b2c0ea9..4ab3fd7 100644 --- a/src/App/Controller/TeamController.php +++ b/src/App/Controller/TeamController.php @@ -4,10 +4,13 @@ namespace IQBall\App\Controller; use IQBall\App\Session\SessionHandle; use IQBall\App\ViewHttpResponse; +use IQBall\Core\Data\Account; +use IQBall\Core\Http\HttpCodes; use IQBall\Core\Http\HttpRequest; use IQBall\Core\Http\HttpResponse; use IQBall\Core\Model\TeamModel; use IQBall\Core\Validation\FieldValidationFail; +use IQBall\Core\Validation\ValidationFail; use IQBall\Core\Validation\Validators; class TeamController { @@ -28,16 +31,6 @@ class TeamController { return ViewHttpResponse::twig("insert_team.html.twig", []); } - - /** - * @param SessionHandle $session - * @return ViewHttpResponse the team panel to add a member - */ - public function displayAddMember(SessionHandle $session): ViewHttpResponse { - return ViewHttpResponse::twig("add_member.html.twig", []); - } - - /** * @param SessionHandle $session * @return ViewHttpResponse the team panel to delete a member @@ -70,7 +63,8 @@ class TeamController { return ViewHttpResponse::twig('insert_team.html.twig', ['bad_fields' => $badFields]); } $teamId = $this->model->createTeam($request['name'], $request['picture'], $request['main_color'], $request['second_color']); - return $this->displayTeam($teamId, $session); + $this->model->addMember($session->getAccount()->getUser()->getEmail(), $teamId, 'COACH'); + return HttpResponse::redirect('/team/' . $teamId); } /** @@ -98,58 +92,155 @@ class TeamController { return ViewHttpResponse::twig('list_team_by_name.html.twig', ['bad_field' => $badField]); } - $teams = $this->model->listByName($request['name']); + $teams = $this->model->listByName($request['name'], $session->getAccount()->getUser()->getId()); if (empty($teams)) { return ViewHttpResponse::twig('display_teams.html.twig', []); } - return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $teams]); } /** + * Delete a team with its id + * @param int $id + * @param SessionHandle $session + * @return HttpResponse + */ + public function deleteTeamById(int $id, SessionHandle $session): HttpResponse { + $a = $session->getAccount(); + $ret = $this->model->deleteTeam($a->getUser()->getEmail(), $id); + if($ret != 0) { + return ViewHttpResponse::twig('display_team.html.twig', ['notDeleted' => true]); + } + return HttpResponse::redirect('/'); + } + + /** + * Display a team with its 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]); + $result = $this->model->getTeam($id, $session->getAccount()->getUser()->getId()); + if($result == null) { + return ViewHttpResponse::twig('error.html.twig', [ + 'failures' => [ValidationFail::unauthorized("Vous n'avez pas accès à cette équipe.")], + ], HttpCodes::FORBIDDEN); + } + $role = $this->model->isCoach($id, $session->getAccount()->getUser()->getEmail()); + + return ViewHttpResponse::react( + 'views/TeamPanel.tsx', + [ + 'team' => [ + "info" => $result->getInfo(), + "members" => $result->listMembers(), + ], + 'isCoach' => $role, + 'currentUserId' => $session->getAccount()->getUser()->getId()] + ); + } + + /** + * @param int $idTeam + * @param SessionHandle $session + * @return ViewHttpResponse the team panel to add a member + */ + public function displayAddMember(int $idTeam, SessionHandle $session): ViewHttpResponse { + return ViewHttpResponse::twig("add_member.html.twig", ['idTeam' => $idTeam]); } /** * add a member to a team + * @param int $idTeam * @param array $request * @param SessionHandle $session * @return HttpResponse */ - public function addMember(array $request, SessionHandle $session): HttpResponse { + public function addMember(int $idTeam, array $request, SessionHandle $session): HttpResponse { $errors = []; - + if(!$this->model->isCoach($idTeam, $session->getAccount()->getUser()->getEmail())) { + return ViewHttpResponse::twig('error.html.twig', [ + 'failures' => [ValidationFail::unauthorized("Vous n'avez pas accès à cette action pour cette équipe.")], + ], HttpCodes::FORBIDDEN); + } $request = HttpRequest::from($request, $errors, [ - "team" => [Validators::isInteger()], "email" => [Validators::email(), Validators::lenBetween(5, 256)], ]); + if(!empty($errors)) { + return ViewHttpResponse::twig('add_member.html.twig', ['badEmail' => true,'idTeam' => $idTeam]); + } + $ret = $this->model->addMember($request['email'], $idTeam, $request['role']); + + switch($ret) { + case -1: + return ViewHttpResponse::twig('add_member.html.twig', ['notFound' => true,'idTeam' => $idTeam]); + case -2: + return ViewHttpResponse::twig('add_member.html.twig', ['alreadyExisting' => true,'idTeam' => $idTeam]); + default: + return HttpResponse::redirect('/team/' . $idTeam); + } + } - $teamId = intval($request['team']); - $this->model->addMember($request['email'], $teamId, $request['role']); + /** + * remove a member from a team with their ids + * @param int $idTeam + * @param int $idMember + * @param SessionHandle $session + * @return HttpResponse + */ + public function deleteMember(int $idTeam, int $idMember, SessionHandle $session): HttpResponse { + if(!$this->model->isCoach($idTeam, $session->getAccount()->getUser()->getEmail())) { + return ViewHttpResponse::twig('error.html.twig', [ + 'failures' => [ValidationFail::unauthorized("Vous n'avez pas accès à cette action pour cette équipe.")], + ], HttpCodes::FORBIDDEN); + } + $teamId = $this->model->deleteMember($idMember, $idTeam); + if($teamId == -1 || $session->getAccount()->getUser()->getId() == $idMember) { + return HttpResponse::redirect('/'); + } return $this->displayTeam($teamId, $session); } /** - * remove a member from a team - * @param array $request + * @param int $idTeam * @param SessionHandle $session - * @return HttpResponse + * @return ViewHttpResponse */ - public function deleteMember(array $request, SessionHandle $session): HttpResponse { - $errors = []; + public function displayEditTeam(int $idTeam, SessionHandle $session): ViewHttpResponse { + return ViewHttpResponse::twig("edit_team.html.twig", ['team' => $this->model->getTeam($idTeam, $session->getAccount()->getUser()->getId())]); + } - $request = HttpRequest::from($request, $errors, [ - "team" => [Validators::isInteger()], - "email" => [Validators::email(), Validators::lenBetween(5, 256)], + /** + * @param int $idTeam + * @param array $request + * @param SessionHandle $session + * @return HttpResponse + */ + public function editTeam(int $idTeam, array $request, SessionHandle $session): HttpResponse { + if(!$this->model->isCoach($idTeam, $session->getAccount()->getUser()->getEmail())) { + return ViewHttpResponse::twig('error.html.twig', [ + 'failures' => [ValidationFail::unauthorized("Vous n'avez pas accès à cette action pour cette équipe.")], + ], HttpCodes::FORBIDDEN); + } + $failures = []; + $request = HttpRequest::from($request, $failures, [ + "name" => [Validators::lenBetween(1, 32), Validators::nameWithSpaces()], + "main_color" => [Validators::hexColor()], + "second_color" => [Validators::hexColor()], + "picture" => [Validators::isURL()], ]); - - return $this->displayTeam($this->model->deleteMember($request['email'], intval($request['team'])), $session); + if (!empty($failures)) { + $badFields = []; + foreach ($failures as $e) { + if ($e instanceof FieldValidationFail) { + $badFields[] = $e->getFieldName(); + } + } + return ViewHttpResponse::twig('edit_team.html.twig', ['bad_fields' => $badFields]); + } + $this->model->editTeam($idTeam, $request['name'], $request['picture'], $request['main_color'], $request['second_color']); + return HttpResponse::redirect('/team/' . $idTeam); } } diff --git a/src/App/Controller/UserController.php b/src/App/Controller/UserController.php index e33ee61..2718843 100644 --- a/src/App/Controller/UserController.php +++ b/src/App/Controller/UserController.php @@ -16,6 +16,7 @@ class UserController { /** * @param TacticModel $tactics + * @param TeamModel|null $teams */ public function __construct(TacticModel $tactics, ?TeamModel $teams = null) { $this->tactics = $tactics; @@ -28,12 +29,15 @@ class UserController { */ public function home(SessionHandle $session): ViewHttpResponse { $limitNbTactics = 5; - $lastTactics = $this->tactics->getLast($limitNbTactics, $session->getAccount()->getId()); - $allTactics = $this->tactics->getAll($session->getAccount()->getId()); - $name = $session->getAccount()->getName(); + + $user = $session->getAccount()->getUser(); + + $lastTactics = $this->tactics->getLast($limitNbTactics, $user->getId()); + $allTactics = $this->tactics->getAll($user->getId()); + $name = $user->getName(); if ($this->teams != null) { - $teams = $this->teams->getAll($session->getAccount()->getId()); + $teams = $this->teams->getAll($user->getId()); } else { $teams = []; } @@ -45,11 +49,7 @@ class UserController { "username" => $name, ]); } - - public function homeTwig(SessionHandle $session): ViewHttpResponse { - return ViewHttpResponse::twig("home.twig", []); - } - + /** * @return ViewHttpResponse account settings page */ diff --git a/src/App/Controller/VisualizerController.php b/src/App/Controller/VisualizerController.php index 631468e..946f6d0 100644 --- a/src/App/Controller/VisualizerController.php +++ b/src/App/Controller/VisualizerController.php @@ -28,7 +28,7 @@ class VisualizerController { public function openVisualizer(int $id, SessionHandle $session): HttpResponse { $tactic = $this->tacticModel->get($id); - $failure = TacticValidator::validateAccess($id, $tactic, $session->getAccount()->getId()); + $failure = TacticValidator::validateAccess($id, $tactic, $session->getAccount()->getUser()->getId()); if ($failure != null) { return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); diff --git a/src/App/Views/add_member.html.twig b/src/App/Views/add_member.html.twig index c6bae0e..cfee16d 100644 --- a/src/App/Views/add_member.html.twig +++ b/src/App/Views/add_member.html.twig @@ -67,28 +67,43 @@ display: flex; justify-content: space-between; } + + .failed{ + color: red; + } +
+

IQBall

+

Ajouter un membre à votre équipe

-
+
- - - - + + + {% if badEmail %} +

Email invalide

+ {% endif %} + {%if notFound %} +

Cette personne n'a pas été trouvé

+ {% endif %} + {% if alreadyExisting %} +

Cette personne est déjà dans l'équipe

+ {% endif %} +
Rôle du membre dans l'équipe :
- +
- +
diff --git a/src/App/Views/display_team.html.twig b/src/App/Views/display_team.html.twig index 7f23b8b..8928e84 100644 --- a/src/App/Views/display_team.html.twig +++ b/src/App/Views/display_team.html.twig @@ -11,10 +11,6 @@ align-items: center; } - section { - width: 60%; - } - .square { width: 50px; height: 50px; @@ -30,19 +26,17 @@ border: solid; } - .container { + section { background-color: #fff; display: flex; flex-direction: column; align-items: center; + width: 60%; } - .team { - border-color: darkgrey; - border-radius: 20px; - + #colors{ + flex-direction: row; } - .color { flex-direction: row; justify-content: space-between; @@ -53,6 +47,16 @@ width: 80px; } + #delete{ + border-radius:10px ; + background-color: red; + color: white; + } + + .player{ + flex-direction: row; + justify-content: space-evenly; + } @@ -61,13 +65,18 @@
- -
+ {% if notDeleted %} + +

Cette équipe ne peut être supprimée.

+
+ {% endif %} +{% if team is defined %} +

{{ team.getInfo().getName() }}

-
+

Couleur principale :

@@ -75,17 +84,26 @@
- + {% if isCoach %} + + + {% endif %} {% for m in team.listMembers() %} -

{{ m.getUserId() }}

- {% if m.getRole().isCoach() %} +
+

{{ m.getUserId() }}

+ {% if m.getRole().isCoach() %}

: Coach

- {% else %} + {% else %}

: Joueur

- {% endif %} + {% endif %} +
{% endfor %}
- +{% else %} +
+

Cette équipe ne peut être affichée

+
+{% endif %}
\ No newline at end of file diff --git a/src/App/Views/display_teams.html.twig b/src/App/Views/display_teams.html.twig index 1e1420a..3e3ab12 100644 --- a/src/App/Views/display_teams.html.twig +++ b/src/App/Views/display_teams.html.twig @@ -3,9 +3,37 @@ Twig view + - +
+

IQBall

+
+
{% if teams is empty %}

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

@@ -22,12 +50,12 @@
{% else %} {% for t in teams %} -
-

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

- logo de l'équipe +
+

Nom de l'équipe : {{ t.getName() }}

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

Modifier votre équipe

+ +
+ + + + + + + + +
+
+ +
+ +
+ + + \ No newline at end of file diff --git a/src/App/Views/home.twig b/src/App/Views/home.twig index 7d8430e..0fc426a 100644 --- a/src/App/Views/home.twig +++ b/src/App/Views/home.twig @@ -74,7 +74,7 @@
{% endfor %} {% else %} -

Aucune équipe créé !

+

Aucune équipe créée !

{% endif %}

Mes strategies

@@ -90,7 +90,7 @@
{% endfor %} {% else %} -

Aucune tactique créé !

+

Aucune tactique créée !

{% endif %} diff --git a/src/App/Views/insert_team.html.twig b/src/App/Views/insert_team.html.twig index 65cd096..0c10114 100644 --- a/src/App/Views/insert_team.html.twig +++ b/src/App/Views/insert_team.html.twig @@ -54,7 +54,6 @@ background-color: #0056b3; } - @@ -68,7 +67,7 @@ - +
diff --git a/src/App/Views/list_team_by_name.html.twig b/src/App/Views/list_team_by_name.html.twig index eca5e19..092a149 100644 --- a/src/App/Views/list_team_by_name.html.twig +++ b/src/App/Views/list_team_by_name.html.twig @@ -7,6 +7,9 @@ body { font-family: Arial, sans-serif; background-color: #f1f1f1; + display: flex; + flex-direction: column; + align-items: center; } .container { @@ -56,7 +59,9 @@ - +
+

IQBall

+

Chercher une équipe

diff --git a/src/Core/Data/Account.php b/src/Core/Data/Account.php index 48b3e69..01f5406 100755 --- a/src/Core/Data/Account.php +++ b/src/Core/Data/Account.php @@ -8,54 +8,34 @@ namespace IQBall\Core\Data; * to share to other users, or non-needed public information */ class Account { - /** - * @var string $email account's mail address - */ - private string $email; - /** * @var string string token */ private string $token; /** - * @var string the account's username + * @var User contains all the account's "public" information */ - private string $name; - - /** - * @var int - */ - private int $id; - + private User $user; /** - * @param string $email - * @param string $name * @param string $token - * @param int $id + * @param User $user */ - public function __construct(string $email, string $name, string $token, int $id) { - $this->email = $email; - $this->name = $name; + public function __construct(string $token, User $user) { $this->token = $token; - $this->id = $id; - } - - public function getId(): int { - return $this->id; - } - - public function getEmail(): string { - return $this->email; + $this->user = $user; } public function getToken(): string { return $this->token; } - public function getName(): string { - return $this->name; + /** + * @return User + */ + public function getUser(): User { + return $this->user; } } diff --git a/src/Core/Data/Color.php b/src/Core/Data/Color.php deleted file mode 100755 index e0cd27c..0000000 --- a/src/Core/Data/Color.php +++ /dev/null @@ -1,44 +0,0 @@ -hex = $value; - } - - /** - * @return string - */ - 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/Core/Data/Member.php b/src/Core/Data/Member.php index d68140c..30e4202 100755 --- a/src/Core/Data/Member.php +++ b/src/Core/Data/Member.php @@ -5,11 +5,8 @@ namespace IQBall\Core\Data; /** * information about a team member */ -class Member { - /** - * @var int The member's user account - */ - private int $userId; +class Member implements \JsonSerializable { + private User $user; /** * @var int The member's team id @@ -17,32 +14,25 @@ class Member { private int $teamId; /** - * @var MemberRole the member's role + * @var string the member's role */ - private MemberRole $role; + private string $role; /** - * @param int $userId - * @param MemberRole $role + * @param User $user + * @param int $teamId + * @param string $role */ - public function __construct(int $userId, int $teamId, MemberRole $role) { - $this->userId = $userId; + public function __construct(User $user, int $teamId, string $role) { + $this->user = $user; $this->teamId = $teamId; $this->role = $role; } - - /** - * @return int - */ - public function getUserId(): int { - return $this->userId; - } - /** - * @return MemberRole + * @return string */ - public function getRole(): MemberRole { + public function getRole(): string { return $this->role; } @@ -52,4 +42,16 @@ class Member { public function getTeamId(): int { return $this->teamId; } + + /** + * @return User + */ + public function getUser(): User { + return $this->user; + } + + + public function jsonSerialize() { + return get_object_vars($this); + } } diff --git a/src/Core/Data/MemberRole.php b/src/Core/Data/MemberRole.php deleted file mode 100755 index 9606c0b..0000000 --- a/src/Core/Data/MemberRole.php +++ /dev/null @@ -1,68 +0,0 @@ -isValid($val)) { - throw new InvalidArgumentException("Valeur du rôle invalide"); - } - $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); - } - - 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); - } - - public function isPlayer(): bool { - return ($this->value == self::ROLE_PLAYER); - } - - public function isCoach(): bool { - return ($this->value == self::ROLE_COACH); - } - -} diff --git a/src/Core/Data/Team.php b/src/Core/Data/Team.php index b8e7834..7adeb49 100755 --- a/src/Core/Data/Team.php +++ b/src/Core/Data/Team.php @@ -2,7 +2,7 @@ namespace IQBall\Core\Data; -class Team { +class Team implements \JsonSerializable { private TeamInfo $info; /** @@ -29,4 +29,10 @@ class Team { public function listMembers(): array { return $this->members; } + + public function jsonSerialize() { + return get_object_vars($this); + } + + } diff --git a/src/Core/Data/TeamInfo.php b/src/Core/Data/TeamInfo.php index 7affcea..0f741fe 100644 --- a/src/Core/Data/TeamInfo.php +++ b/src/Core/Data/TeamInfo.php @@ -2,21 +2,21 @@ namespace IQBall\Core\Data; -class TeamInfo { +class TeamInfo implements \JsonSerializable { private int $id; private string $name; private string $picture; - private Color $mainColor; - private Color $secondColor; + private string $mainColor; + private string $secondColor; /** * @param int $id * @param string $name * @param string $picture - * @param Color $mainColor - * @param Color $secondColor + * @param string $mainColor + * @param string $secondColor */ - public function __construct(int $id, string $name, string $picture, Color $mainColor, Color $secondColor) { + public function __construct(int $id, string $name, string $picture, string $mainColor, string $secondColor) { $this->id = $id; $this->name = $name; $this->picture = $picture; @@ -37,13 +37,17 @@ class TeamInfo { return $this->picture; } - public function getMainColor(): Color { + public function getMainColor(): string { return $this->mainColor; } - public function getSecondColor(): Color { + public function getSecondColor(): string { return $this->secondColor; } + public function jsonSerialize() { + return get_object_vars($this); + } + } diff --git a/src/Core/Data/User.php b/src/Core/Data/User.php new file mode 100644 index 0000000..71e0dd1 --- /dev/null +++ b/src/Core/Data/User.php @@ -0,0 +1,72 @@ +email = $email; + $this->name = $name; + $this->id = $id; + $this->profilePicture = $profilePicture; + } + + /** + * @return string + */ + public function getEmail(): string { + return $this->email; + } + + /** + * @return string + */ + public function getName(): string { + return $this->name; + } + + /** + * @return int + */ + public function getId(): int { + return $this->id; + } + + /** + * @return string + */ + public function getProfilePicture(): string { + return $this->profilePicture; + } + + public function jsonSerialize() { + return get_object_vars($this); + } +} diff --git a/src/Core/Gateway/AccountGateway.php b/src/Core/Gateway/AccountGateway.php index 7740b57..a9c3e18 100644 --- a/src/Core/Gateway/AccountGateway.php +++ b/src/Core/Gateway/AccountGateway.php @@ -4,6 +4,7 @@ namespace IQBall\Core\Gateway; use IQBall\Core\Connection; use IQBall\Core\Data\Account; +use IQBall\Core\Data\User; use PDO; class AccountGateway { @@ -16,13 +17,13 @@ class AccountGateway { $this->con = $con; } - - public function insertAccount(string $name, string $email, string $token, string $hash): int { - $this->con->exec("INSERT INTO Account(username, hash, email, token) VALUES (:username,:hash,:email,:token)", [ + public function insertAccount(string $name, string $email, string $token, string $hash, string $profilePicture): int { + $this->con->exec("INSERT INTO Account(username, hash, email, token,profilePicture) VALUES (:username,:hash,:email,:token,:profilePic)", [ ':username' => [$name, PDO::PARAM_STR], ':hash' => [$hash, PDO::PARAM_STR], ':email' => [$email, PDO::PARAM_STR], ':token' => [$token, PDO::PARAM_STR], + ':profilePic' => [$profilePicture, PDO::PARAM_STR], ]); return intval($this->con->lastInsertId()); } @@ -65,7 +66,7 @@ class AccountGateway { return null; } - return new Account($email, $acc["username"], $acc["token"], $acc["id"]); + return new Account($acc["token"], new User($email, $acc["username"], $acc["id"], $acc["profilePicture"])); } /** @@ -78,7 +79,7 @@ class AccountGateway { return null; } - return new Account($acc["email"], $acc["username"], $acc["token"], $acc["id"]); + return new Account($acc["token"], new User($acc["email"], $acc["username"], $acc["id"], $acc["profilePicture"])); } diff --git a/src/Core/Gateway/MemberGateway.php b/src/Core/Gateway/MemberGateway.php index 999bf10..a5116e8 100644 --- a/src/Core/Gateway/MemberGateway.php +++ b/src/Core/Gateway/MemberGateway.php @@ -4,7 +4,7 @@ namespace IQBall\Core\Gateway; use IQBall\Core\Connection; use IQBall\Core\Data\Member; -use IQBall\Core\Data\MemberRole; +use IQBall\Core\Data\User; use PDO; class MemberGateway { @@ -41,13 +41,12 @@ class MemberGateway { */ 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", + "SELECT a.id,a.email,a.username,a.profilePicture,m.role 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); + return array_map(fn($row) => new Member(new User($row['email'], $row['username'], $row['id'], $row['profilePicture']), $teamId, $row['role']), $rows); } /** @@ -66,4 +65,36 @@ class MemberGateway { ); } + /** + * @param string $email + * @param int $idTeam + * @return bool + */ + public function isCoach(string $email, int $idTeam): bool { + $result = $this->con->fetch( + "SELECT role FROM Member WHERE id_team=:team AND id_user = (SELECT id FROM Account WHERE email=:email)", + [ + "team" => [$idTeam, PDO::PARAM_INT], + "email" => [$email, PDO::PARAM_STR], + ] + )[0]['role']; + + return $result == 'COACH'; + } + + /** + * @param int $idTeam + * @param int $idCurrentUser + * @return bool + */ + public function isMemberOfTeam(int $idTeam, int $idCurrentUser): bool { + $result = $this->con->fetch( + "SELECT id_user FROM Member WHERE id_team = :team AND id_user = :user", + [ + "team" => [$idTeam, PDO::PARAM_INT], + "user" => [$idCurrentUser, PDO::PARAM_INT], + ] + ); + return !empty($result); + } } diff --git a/src/Core/Gateway/TeamGateway.php b/src/Core/Gateway/TeamGateway.php index bc1da94..fe0b2bb 100644 --- a/src/Core/Gateway/TeamGateway.php +++ b/src/Core/Gateway/TeamGateway.php @@ -3,7 +3,6 @@ namespace IQBall\Core\Gateway; use IQBall\Core\Connection; -use IQBall\Core\Data\Color; use IQBall\Core\Data\TeamInfo; use PDO; @@ -23,7 +22,7 @@ class TeamGateway { */ public function insert(string $name, string $picture, string $mainColor, string $secondColor): int { $this->con->exec( - "INSERT INTO Team(name, picture, main_color, second_color) VALUES (:team_name , :picture, :main_color, :second_color)", + "INSERT INTO team(name, picture, main_color, second_color) VALUES (:team_name , :picture, :main_color, :second_color)", [ ":team_name" => [$name, PDO::PARAM_STR], ":picture" => [$picture, PDO::PARAM_STR], @@ -34,38 +33,37 @@ class TeamGateway { return intval($this->con->lastInsertId()); } - /** * @param string $name + * @param int $id * @return TeamInfo[] */ - public function listByName(string $name): array { + public function listByName(string $name, int $id): array { $result = $this->con->fetch( - "SELECT * FROM Team WHERE name LIKE '%' || :name || '%'", + "SELECT t.* FROM team t, Member m WHERE t.name LIKE '%' || :name || '%' AND t.id=m.id_team AND m.id_user=:id", [ ":name" => [$name, PDO::PARAM_STR], + "id" => [$id, PDO::PARAM_INT], ] ); - - return array_map(fn($row) => new TeamInfo($row['id'], $row['name'], $row['picture'], Color::from($row['main_color']), Color::from($row['second_color'])), $result); + return array_map(fn($row) => new TeamInfo($row['id'], $row['name'], $row['picture'], $row['main_color'], $row['second_color']), $result); } /** * @param int $id - * @return TeamInfo + * @return TeamInfo|null */ public function getTeamById(int $id): ?TeamInfo { $row = $this->con->fetch( - "SELECT * FROM Team WHERE id = :id", + "SELECT * FROM team WHERE id = :id", [ - ":id" => [$id, PDO::PARAM_INT], - ] + ":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'])); + return new TeamInfo($row['id'], $row['name'], $row['picture'], $row['main_color'], $row['second_color']); } /** @@ -74,16 +72,60 @@ class TeamGateway { */ public function getTeamIdByName(string $name): ?int { return $this->con->fetch( - "SELECT id FROM Team WHERE name = :name", + "SELECT id FROM team WHERE name = :name", [ - ":name" => [$name, PDO::PARAM_INT], - ] + ":name" => [$name, PDO::PARAM_INT], + ] )[0]['id'] ?? null; } + /** + * @param int $idTeam + */ + public function deleteTeam(int $idTeam): void { + $this->con->exec( + "DELETE FROM Member WHERE id_team=:team", + [ + "team" => [$idTeam, PDO::PARAM_INT], + ] + ); + $this->con->exec( + "DELETE FROM TEAM WHERE id=:team", + [ + "team" => [$idTeam, PDO::PARAM_INT], + ] + ); + } + + /** + * @param int $idTeam + * @param string $newName + * @param string $newPicture + * @param string $newMainColor + * @param string $newSecondColor + * @return void + */ + public function editTeam(int $idTeam, string $newName, string $newPicture, string $newMainColor, string $newSecondColor) + { + $this->con->exec( + "UPDATE team + SET name = :newName, + picture = :newPicture, + main_color = :newMainColor, + second_color = :newSecondColor + WHERE id = :team", + [ + "team" => [$idTeam, PDO::PARAM_INT], + "newName" => [$newName, PDO::PARAM_STR], + "newPicture" => [$newPicture, PDO::PARAM_STR], + "newMainColor" => [$newMainColor, PDO::PARAM_STR], + "newSecondColor" => [$newSecondColor, PDO::PARAM_STR], + ] + ); + + } /** * Get all the user's teams - * * @param integer $user * @return array> */ diff --git a/src/Core/Model/AuthModel.php b/src/Core/Model/AuthModel.php index 929eb99..bc29248 100644 --- a/src/Core/Model/AuthModel.php +++ b/src/Core/Model/AuthModel.php @@ -2,13 +2,16 @@ namespace IQBall\Core\Model; +use Exception; use IQBall\Core\Data\Account; +use IQBall\Core\Data\User; use IQBall\Core\Gateway\AccountGateway; use IQBall\Core\Validation\FieldValidationFail; use IQBall\Core\Validation\ValidationFail; class AuthModel { private AccountGateway $gateway; + private const DEFAULT_PROFILE_PICTURE = "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png"; /** * @param AccountGateway $gateway @@ -17,7 +20,6 @@ class AuthModel { $this->gateway = $gateway; } - /** * @param string $username * @param string $password @@ -25,6 +27,7 @@ class AuthModel { * @param string $email * @param ValidationFail[] $failures * @return Account|null the registered account or null if failures occurred + * @throws Exception */ public function register(string $username, string $password, string $confirmPassword, string $email, array &$failures): ?Account { @@ -43,13 +46,14 @@ class AuthModel { $hash = password_hash($password, PASSWORD_DEFAULT); $token = $this->generateToken(); - $accountId = $this->gateway->insertAccount($username, $email, $token, $hash); - return new Account($email, $username, $token, $accountId); + $accountId = $this->gateway->insertAccount($username, $email, $token, $hash, self::DEFAULT_PROFILE_PICTURE); + return new Account($token, new User($email, $username, $accountId, self::DEFAULT_PROFILE_PICTURE)); } /** * Generate a random base 64 string * @return string + * @throws Exception */ private function generateToken(): string { return base64_encode(random_bytes(64)); @@ -70,5 +74,4 @@ class AuthModel { return $this->gateway->getAccountFromMail($email); } - } diff --git a/src/Core/Model/TeamModel.php b/src/Core/Model/TeamModel.php index a5ba84b..d6c97ce 100644 --- a/src/Core/Model/TeamModel.php +++ b/src/Core/Model/TeamModel.php @@ -2,7 +2,6 @@ namespace IQBall\Core\Model; -use IQBall\Core\Data\Color; use IQBall\Core\Data\Team; use IQBall\Core\Data\TeamInfo; use IQBall\Core\Gateway\AccountGateway; @@ -26,6 +25,7 @@ class TeamModel { } /** + * Create a team * @param string $name * @param string $picture * @param string $mainColor @@ -37,48 +37,100 @@ class TeamModel { } /** - * adds a member to a team + * add a member to a team * @param string $mail * @param int $teamId * @param string $role - * @return void + * @return int */ - public function addMember(string $mail, int $teamId, string $role): void { - $userId = $this->users->getAccountFromMail($mail)->getId(); - $this->members->insert($teamId, $userId, $role); + public function addMember(string $mail, int $teamId, string $role): int { + $user = $this->users->getAccountFromMail($mail); + if($user == null) { + return -1; + } + if(!$this->members->isMemberOfTeam($teamId, $user->getUser()->getId())) { + $this->members->insert($teamId, $user->getUser()->getId(), $role); + return 1; + } + return -2; } /** * @param string $name + * @param int $id * @return TeamInfo[] */ - public function listByName(string $name): array { - return $this->teams->listByName($name); + public function listByName(string $name, int $id): array { + return $this->teams->listByName($name, $id); } /** - * @param int $id - * @return Team + * @param int $idTeam + * @param int $idCurrentUser + * @return Team|null */ - public function getTeam(int $id): Team { - $teamInfo = $this->teams->getTeamById($id); - $members = $this->members->getMembersOfTeam($id); + public function getTeam(int $idTeam, int $idCurrentUser): ?Team { + if(!$this->members->isMemberOfTeam($idTeam, $idCurrentUser)) { + return null; + } + $teamInfo = $this->teams->getTeamById($idTeam); + $members = $this->members->getMembersOfTeam($idTeam); return new Team($teamInfo, $members); } - /** * delete a member from given team identifier - * @param string $mail + * @param int $idMember * @param int $teamId * @return int */ - public function deleteMember(string $mail, int $teamId): int { - $userId = $this->users->getAccountFromMail($mail)->getId(); - $this->members->remove($teamId, $userId); + public function deleteMember(int $idMember, int $teamId): int { + $this->members->remove($teamId, $idMember); + if(empty($this->members->getMembersOfTeam($teamId))) { + $this->teams->deleteTeam($teamId); + return -1; + } return $teamId; } + /** + * Delete a team + * @param string $email + * @param int $idTeam + * @return int + */ + public function deleteTeam(string $email, int $idTeam): int { + if($this->members->isCoach($email, $idTeam)) { + $this->teams->deleteTeam($idTeam); + return 0; + } + return -1; + } + + /** + * Verify if the account associated to an email is in a specific team indicated with its id + * @param int $idTeam + * @param string $email + * @return bool + */ + public function isCoach(int $idTeam, string $email): bool { + return $this->members->isCoach($email, $idTeam); + } + + /** + * Edit a team with its id, and replace the current attributes with the new ones + * @param int $idTeam + * @param string $newName + * @param string $newPicture + * @param string $newMainColor + * @param string $newSecondColor + * @return void + */ + public function editTeam(int $idTeam, string $newName, string $newPicture, string $newMainColor, string $newSecondColor) + { + $this->teams->editTeam($idTeam, $newName, $newPicture, $newMainColor, $newSecondColor); + } + /** * Get all user's teams * @@ -88,5 +140,4 @@ class TeamModel { public function getAll(int $user): array { return $this->teams->getAll($user); } - }