Team's part (#84)
continuous-integration/drone/push Build is passing Details

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 <Mael.DAIM@etu.uca.fr>
Co-authored-by: maxime <maximebatista18@gmail.com>
Reviewed-on: #84
Co-authored-by: Maël DAIM <mael.daim@etu.uca.fr>
Co-committed-by: Maël DAIM <mael.daim@etu.uca.fr>
pull/92/head
Maël DAIM 1 year ago committed by Maxime BATISTA
parent 939a611e45
commit 3a437a7ad1

@ -9,12 +9,13 @@ import {
} from "react" } from "react"
import CourtPlayer from "./CourtPlayer" 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 ArrowAction from "../actions/ArrowAction"
import { middlePos, ratioWithinBase } from "../arrows/Pos" import { middlePos, ratioWithinBase } from "../arrows/Pos"
import BallAction from "../actions/BallAction" import BallAction from "../actions/BallAction"
import { CourtObject } from "../../tactic/CourtObjects" import { CourtObject } from "../../model/tactic/Ball"
import { contains } from "../arrows/Box" import { contains } from "../arrows/Box"
import { CourtAction } from "../../views/editor/CourtAction" 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 && ( {previewAction && (

@ -1,7 +1,7 @@
import React, { useRef } from "react" import React, { useRef } from "react"
import Draggable from "react-draggable" import Draggable from "react-draggable"
import { BallPiece } from "./BallPiece" import { BallPiece } from "./BallPiece"
import { Ball } from "../../tactic/CourtObjects" import { Ball } from "../../model/tactic/Ball"
export interface CourtBallProps { export interface CourtBallProps {
onMoved: (rect: DOMRect) => void onMoved: (rect: DOMRect) => void

@ -2,7 +2,7 @@ import { ReactNode, RefObject, useRef } from "react"
import "../../style/player.css" import "../../style/player.css"
import Draggable from "react-draggable" import Draggable from "react-draggable"
import { PlayerPiece } from "./PlayerPiece" import { PlayerPiece } from "./PlayerPiece"
import { Player } from "../../tactic/Player" import { Player } from "../../model/tactic/Player"
import { NULL_POS, ratioWithinBase } from "../arrows/Pos" import { NULL_POS, ratioWithinBase } from "../arrows/Pos"
export interface PlayerProps { export interface PlayerProps {

@ -1,8 +1,8 @@
import "../../style/player.css" import "../../style/player.css"
import { Team } from "../../tactic/Team" import { PlayerTeam } from "../../model/tactic/Player"
export interface PlayerPieceProps { export interface PlayerPieceProps {
team: Team team: PlayerTeam
text: string text: string
hasBall: boolean hasBall: boolean
} }

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

@ -0,0 +1,6 @@
export interface User {
id: number
name: string
email: string
profilePicture: string
}

@ -1,5 +1,5 @@
import { Pos } from "../components/arrows/Pos" import { Pos } from "../../components/arrows/Pos"
import { Segment } from "../components/arrows/BendableArrow" import { Segment } from "../../components/arrows/BendableArrow"
import { PlayerId } from "./Player" import { PlayerId } from "./Player"
export enum ActionKind { export enum ActionKind {

@ -1,14 +1,17 @@
import { Team } from "./Team"
export type PlayerId = string export type PlayerId = string
export enum PlayerTeam {
Allies = "allies",
Opponents = "opponents",
}
export interface Player { export interface Player {
readonly id: PlayerId readonly id: PlayerId
/** /**
* the player's team * the player's team
* */ * */
readonly team: Team readonly team: PlayerTeam
/** /**
* player's role * player's role

@ -1,5 +1,5 @@
import { Player } from "./Player" import { Player } from "./Player"
import { CourtObject } from "./CourtObjects" import { CourtObject } from "./Ball"
import { Action } from "./Action" import { Action } from "./Action"
export interface Tactic { export interface Tactic {

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

@ -1,4 +0,0 @@
export enum Team {
Allies = "allies",
Opponents = "opponents",
}

@ -16,22 +16,22 @@ import { BallPiece } from "../components/editor/BallPiece"
import { Rack } from "../components/Rack" import { Rack } from "../components/Rack"
import { PlayerPiece } from "../components/editor/PlayerPiece" 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 { fetchAPI } from "../Fetcher"
import { Team } from "../tactic/Team" import { PlayerTeam } from "../model/tactic/Player"
import SavingState, { import SavingState, {
SaveState, SaveState,
SaveStates, SaveStates,
} from "../components/editor/SavingState" } from "../components/editor/SavingState"
import { CourtObject } from "../tactic/CourtObjects" import { CourtObject } from "../model/tactic/Ball"
import { CourtAction } from "./editor/CourtAction" import { CourtAction } from "./editor/CourtAction"
import { BasketCourt } from "../components/editor/BasketCourt" import { BasketCourt } from "../components/editor/BasketCourt"
import { ratioWithinBase } from "../components/arrows/Pos" import { ratioWithinBase } from "../components/arrows/Pos"
import { Action, ActionKind } from "../tactic/Action" import { Action, ActionKind } from "../model/tactic/Action"
const ERROR_STYLE: CSSProperties = { const ERROR_STYLE: CSSProperties = {
borderColor: "red", borderColor: "red",
@ -58,7 +58,7 @@ export interface EditorProps {
* information about a player that is into a rack * information about a player that is into a rack
*/ */
interface RackedPlayer { interface RackedPlayer {
team: Team team: PlayerTeam
key: string key: string
} }
@ -134,10 +134,10 @@ function EditorView({
) )
const [allies, setAllies] = useState( const [allies, setAllies] = useState(
getRackPlayers(Team.Allies, content.players), getRackPlayers(PlayerTeam.Allies, content.players),
) )
const [opponents, setOpponents] = useState( const [opponents, setOpponents] = useState(
getRackPlayers(Team.Opponents, content.players), getRackPlayers(PlayerTeam.Opponents, content.players),
) )
const [objects, setObjects] = useState<RackedCourtObject[]>( const [objects, setObjects] = useState<RackedCourtObject[]>(
@ -222,7 +222,7 @@ function EditorView({
break break
default: default:
throw new Error("unknown court object ", rackedObject.key) throw new Error("unknown court object " + rackedObject.key)
} }
setContent((content) => { setContent((content) => {
@ -357,10 +357,10 @@ function EditorView({
})) }))
let setter let setter
switch (player.team) { switch (player.team) {
case Team.Opponents: case PlayerTeam.Opponents:
setter = setOpponents setter = setOpponents
break break
case Team.Allies: case PlayerTeam.Allies:
setter = setAllies setter = setAllies
} }
if (player.hasBall) { if (player.hasBall) {
@ -539,7 +539,7 @@ function renderCourtObject(courtObject: RackedCourtObject) {
if (courtObject.key == "ball") { if (courtObject.key == "ball") {
return <BallPiece /> return <BallPiece />
} }
throw new Error("unknown racked court object ", courtObject.key) throw new Error("unknown racked court object " + courtObject.key)
} }
function Court({ courtType }: { courtType: string }) { 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"] return ["1", "2", "3", "4", "5"]
.filter( .filter(
(role) => (role) =>

@ -162,7 +162,7 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) {
function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) { function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) {
let data let data
if (allTactics.length == 0) { if (allTactics.length == 0) {
data = <p>Aucune tactique créé !</p> data = <p>Aucune tactique créée !</p>
} else { } else {
data = <TableData allTactics={allTactics} /> data = <TableData allTactics={allTactics} />
} }

@ -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 (
<div id="main-div">
<header>
<h1>
<a href={BASE + "/"}>IQBall</a>
</h1>
</header>
<TeamDisplay team={team.info} />
{isCoach && <CoachOptions id={team.info.id} />}
<MembersDisplay
members={team.members}
isCoach={isCoach}
idTeam={team.info.id}
currentUserId={currentUserId}
/>
</div>
)
}
function TeamDisplay({ team }: { team: TeamInfo }) {
return (
<div id="team-info">
<div id="first-part">
<h1 id="team-name">{team.name}</h1>
<img id="logo" src={team.picture} alt="Logo d'équipe" />
</div>
<div id="colors">
<div id="colorsTitle">
<p>Couleur principale</p>
<p>Couleur secondaire</p>
</div>
<div id="actual-colors">
<ColorDisplay color={team.mainColor} />
<ColorDisplay color={team.secondColor} />
</div>
</div>
</div>
)
}
function ColorDisplay({ color }: { color: string }) {
return <div className="square" style={{ backgroundColor: color }} />
}
function CoachOptions({ id }: { id: number }) {
return (
<div>
<button
id="delete"
onClick={() =>
confirm("Êtes-vous sûr de supprimer cette équipe?")
? (window.location.href = `${BASE}/team/${id}/delete`)
: {}
}>
Supprimer
</button>
<button
id="edit"
onClick={() =>
(window.location.href = `${BASE}/team/${id}/edit`)
}>
Modifier
</button>
</div>
)
}
function MembersDisplay({
members,
isCoach,
idTeam,
currentUserId,
}: {
members: Member[]
isCoach: boolean
idTeam: number
currentUserId: number
}) {
const listMember = members.map((member) => (
<MemberDisplay
member={member}
isCoach={isCoach}
idTeam={idTeam}
currentUserId={currentUserId}
/>
))
return (
<div id="members">
<div id="head-members">
<h2>Membres :</h2>
{isCoach && (
<button
id="add-member"
onClick={() =>
(window.location.href = `${BASE}/team/${idTeam}/addMember`)
}>
+
</button>
)}
</div>
{listMember}
</div>
)
}
function MemberDisplay({
member,
isCoach,
idTeam,
currentUserId,
}: {
member: Member
isCoach: boolean
idTeam: number
currentUserId: number
}) {
return (
<div className="member">
<img
id="profile-picture"
src={member.user.profilePicture}
alt="Photo de profile"
/>
<p>{member.user.name}</p>
<p>{member.role}</p>
<p>{member.user.email}</p>
{isCoach && currentUserId !== member.user.id && (
<button
id="delete"
onClick={() =>
confirm(
"Êtes-vous sûr de retirer ce membre de l'équipe?",
)
? (window.location.href = `${BASE}/team/${idTeam}/remove/${member.user.id}`)
: {}
}>
Retirer
</button>
)}
{isCoach && currentUserId == member.user.id && (
<button
id="delete"
onClick={() =>
confirm("Êtes-vous sûr de quitter cette équipe?")
? (window.location.href = `${BASE}/team/${idTeam}/remove/${member.user.id}`)
: {}
}>
Quitter
</button>
)}
</div>
)
}

@ -1,4 +1,4 @@
import { Action, ActionKind } from "../../tactic/Action" import { Action, ActionKind } from "../../model/tactic/Action"
import BendableArrow from "../../components/arrows/BendableArrow" import BendableArrow from "../../components/arrows/BendableArrow"
import { RefObject } from "react" import { RefObject } from "react"
import { MoveToHead, ScreenHead } from "../../components/actions/ArrowAction" import { MoveToHead, ScreenHead } from "../../components/actions/ArrowAction"

@ -51,7 +51,6 @@ function tryGetAuthorization(): ?Account {
$session = PhpSessionHandle::init(); $session = PhpSessionHandle::init();
return $session->getAccount(); return $session->getAccount();
} }
$token = $headers['Authorization']; $token = $headers['Authorization'];
$gateway = new AccountGateway(new Connection(get_database())); $gateway = new AccountGateway(new Connection(get_database()));
return $gateway->getAccountFromToken($token); return $gateway->getAccountFromToken($token);

@ -102,10 +102,13 @@ function getRoutes(): AltoRouter {
$ar->map("GET", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->displayListTeamByName($s))); $ar->map("GET", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->displayListTeamByName($s)));
$ar->map("POST", "/team/search", Action::auth(fn(SessionHandle $s) => getTeamController()->listTeamByName($_POST, $s))); $ar->map("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/[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("GET", "/team/[i:id]/delete", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->deleteTeamById($id, $s)));
$ar->map("POST", "/team/members/add", Action::auth(fn(SessionHandle $s) => getTeamController()->addMember($_POST, $s))); $ar->map("GET", "/team/[i:id]/addMember", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->displayAddMember($id, $s)));
$ar->map("GET", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->displayDeleteMember($s))); $ar->map("POST", "/team/[i:id]/addMember", Action::auth(fn(int $id, SessionHandle $s) => getTeamController()->addMember($id, $_POST, $s)));
$ar->map("POST", "/team/members/remove", Action::auth(fn(SessionHandle $s) => getTeamController()->deleteMember($_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; return $ar;
} }

@ -10,7 +10,8 @@ CREATE TABLE Account
email varchar UNIQUE NOT NULL, email varchar UNIQUE NOT NULL,
username varchar NOT NULL, username varchar NOT NULL,
token varchar UNIQUE NOT NULL, token varchar UNIQUE NOT NULL,
hash varchar NOT NULL hash varchar NOT NULL,
profilePicture varchar NOT NULL
); );
CREATE TABLE Tactic CREATE TABLE Tactic

@ -36,7 +36,7 @@ class APITacticController {
"name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()], "name" => [Validators::lenBetween(1, 50), Validators::nameWithSpaces()],
], function (HttpRequest $request) use ($tactic_id, $account) { ], 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)) { if (!empty($failures)) {
//TODO find a system to handle Unauthorized error codes more easily from failures. //TODO find a system to handle Unauthorized error codes more easily from failures.

@ -63,7 +63,7 @@ class EditorController {
return $this->openTestEditor($type); 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); return $this->openEditorFor($tactic);
} }
@ -76,7 +76,7 @@ class EditorController {
public function openEditor(int $id, SessionHandle $session): ViewHttpResponse { public function openEditor(int $id, SessionHandle $session): ViewHttpResponse {
$tactic = $this->model->get($id); $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) { if ($failure != null) {
return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND);

@ -4,10 +4,13 @@ namespace IQBall\App\Controller;
use IQBall\App\Session\SessionHandle; use IQBall\App\Session\SessionHandle;
use IQBall\App\ViewHttpResponse; use IQBall\App\ViewHttpResponse;
use IQBall\Core\Data\Account;
use IQBall\Core\Http\HttpCodes;
use IQBall\Core\Http\HttpRequest; use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse; use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Model\TeamModel; use IQBall\Core\Model\TeamModel;
use IQBall\Core\Validation\FieldValidationFail; use IQBall\Core\Validation\FieldValidationFail;
use IQBall\Core\Validation\ValidationFail;
use IQBall\Core\Validation\Validators; use IQBall\Core\Validation\Validators;
class TeamController { class TeamController {
@ -28,16 +31,6 @@ class TeamController {
return ViewHttpResponse::twig("insert_team.html.twig", []); 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 * @param SessionHandle $session
* @return ViewHttpResponse the team panel to delete a member * @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]); return ViewHttpResponse::twig('insert_team.html.twig', ['bad_fields' => $badFields]);
} }
$teamId = $this->model->createTeam($request['name'], $request['picture'], $request['main_color'], $request['second_color']); $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]); 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)) { if (empty($teams)) {
return ViewHttpResponse::twig('display_teams.html.twig', []); return ViewHttpResponse::twig('display_teams.html.twig', []);
} }
return ViewHttpResponse::twig('display_teams.html.twig', ['teams' => $teams]); 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 int $id
* @param SessionHandle $session * @param SessionHandle $session
* @return ViewHttpResponse a view that displays given team information * @return ViewHttpResponse a view that displays given team information
*/ */
public function displayTeam(int $id, SessionHandle $session): ViewHttpResponse { public function displayTeam(int $id, SessionHandle $session): ViewHttpResponse {
$result = $this->model->getTeam($id); $result = $this->model->getTeam($id, $session->getAccount()->getUser()->getId());
return ViewHttpResponse::twig('display_team.html.twig', ['team' => $result]); 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 * add a member to a team
* @param int $idTeam
* @param array<string, mixed> $request * @param array<string, mixed> $request
* @param SessionHandle $session * @param SessionHandle $session
* @return HttpResponse * @return HttpResponse
*/ */
public function addMember(array $request, SessionHandle $session): HttpResponse { public function addMember(int $idTeam, array $request, SessionHandle $session): HttpResponse {
$errors = []; $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, [ $request = HttpRequest::from($request, $errors, [
"team" => [Validators::isInteger()],
"email" => [Validators::email(), Validators::lenBetween(5, 256)], "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); return $this->displayTeam($teamId, $session);
} }
/** /**
* remove a member from a team * @param int $idTeam
* @param array<string, mixed> $request
* @param SessionHandle $session * @param SessionHandle $session
* @return HttpResponse * @return ViewHttpResponse
*/ */
public function deleteMember(array $request, SessionHandle $session): HttpResponse { public function displayEditTeam(int $idTeam, SessionHandle $session): ViewHttpResponse {
$errors = []; return ViewHttpResponse::twig("edit_team.html.twig", ['team' => $this->model->getTeam($idTeam, $session->getAccount()->getUser()->getId())]);
}
$request = HttpRequest::from($request, $errors, [ /**
"team" => [Validators::isInteger()], * @param int $idTeam
"email" => [Validators::email(), Validators::lenBetween(5, 256)], * @param array<string,mixed> $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()],
]); ]);
if (!empty($failures)) {
return $this->displayTeam($this->model->deleteMember($request['email'], intval($request['team'])), $session); $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);
} }
} }

@ -16,6 +16,7 @@ class UserController {
/** /**
* @param TacticModel $tactics * @param TacticModel $tactics
* @param TeamModel|null $teams
*/ */
public function __construct(TacticModel $tactics, ?TeamModel $teams = null) { public function __construct(TacticModel $tactics, ?TeamModel $teams = null) {
$this->tactics = $tactics; $this->tactics = $tactics;
@ -28,12 +29,15 @@ class UserController {
*/ */
public function home(SessionHandle $session): ViewHttpResponse { public function home(SessionHandle $session): ViewHttpResponse {
$limitNbTactics = 5; $limitNbTactics = 5;
$lastTactics = $this->tactics->getLast($limitNbTactics, $session->getAccount()->getId());
$allTactics = $this->tactics->getAll($session->getAccount()->getId()); $user = $session->getAccount()->getUser();
$name = $session->getAccount()->getName();
$lastTactics = $this->tactics->getLast($limitNbTactics, $user->getId());
$allTactics = $this->tactics->getAll($user->getId());
$name = $user->getName();
if ($this->teams != null) { if ($this->teams != null) {
$teams = $this->teams->getAll($session->getAccount()->getId()); $teams = $this->teams->getAll($user->getId());
} else { } else {
$teams = []; $teams = [];
} }
@ -46,10 +50,6 @@ class UserController {
]); ]);
} }
public function homeTwig(SessionHandle $session): ViewHttpResponse {
return ViewHttpResponse::twig("home.twig", []);
}
/** /**
* @return ViewHttpResponse account settings page * @return ViewHttpResponse account settings page
*/ */

@ -28,7 +28,7 @@ class VisualizerController {
public function openVisualizer(int $id, SessionHandle $session): HttpResponse { public function openVisualizer(int $id, SessionHandle $session): HttpResponse {
$tactic = $this->tacticModel->get($id); $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) { if ($failure != null) {
return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND); return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND);

@ -67,28 +67,43 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.failed{
color: red;
}
</style> </style>
</head> </head>
<body> <body>
<header>
<h1><a href="{{ path('/') }}">IQBall</a></h1>
</header>
<div class="container"> <div class="container">
<h2>Ajouter un membre à votre équipe</h2> <h2>Ajouter un membre à votre équipe</h2>
<form action="{{ path('/team/members/add') }}" method="POST"> <form action="{{ path("/team/#{idTeam}/addMember") }}" method="POST">
<div class="form-group"> <div class="form-group">
<label for="team">Team où ajouter le membre :</label>
<input type="text" id="team" name="team" required> <label for="email">Email du membre :</label>
<label for="mail">Email du membre :</label> {% if badEmail %}
<input type="text" id="mail" name="mail" required> <p class="failed">Email invalide</p>
{% endif %}
{%if notFound %}
<p class="failed">Cette personne n'a pas été trouvé</p>
{% endif %}
{% if alreadyExisting %}
<p class="failed">Cette personne est déjà dans l'équipe</p>
{% endif %}
<input type="text" id="email" name="email" required>
<fieldset class="role"> <fieldset class="role">
<legend>Rôle du membre dans l'équipe :</legend> <legend>Rôle du membre dans l'équipe :</legend>
<div class="radio"> <div class="radio">
<label for="P">Joueur</label> <label for="P">Joueur</label>
<input type="radio" id="P" name="role" value="P" checked /> <input type="radio" id="P" name="role" value="PLAYER" checked />
</div> </div>
<div class="radio"> <div class="radio">
<label for="C">Coach</label> <label for="C">Coach</label>
<input type="radio" id="C" name="role" value="C" /> <input type="radio" id="C" name="role" value="COACH" />
</div> </div>
</fieldset> </fieldset>

@ -11,10 +11,6 @@
align-items: center; align-items: center;
} }
section {
width: 60%;
}
.square { .square {
width: 50px; width: 50px;
height: 50px; height: 50px;
@ -30,19 +26,17 @@
border: solid; border: solid;
} }
.container { section {
background-color: #fff; background-color: #fff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 60%;
} }
.team { #colors{
border-color: darkgrey; flex-direction: row;
border-radius: 20px;
} }
.color { .color {
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
@ -53,6 +47,16 @@
width: 80px; width: 80px;
} }
#delete{
border-radius:10px ;
background-color: red;
color: white;
}
.player{
flex-direction: row;
justify-content: space-evenly;
}
</style> </style>
</head> </head>
<body> <body>
@ -61,13 +65,18 @@
</header> </header>
<section class="container"> <section class="container">
{% if notDeleted %}
<div class="team container"> <popup>
<p>Cette équipe ne peut être supprimée.</p>
</popup>
{% endif %}
{% if team is defined %}
<div class="team">
<div> <div>
<h1>{{ team.getInfo().getName() }}</h1> <h1>{{ team.getInfo().getName() }}</h1>
<img src="{{ team.getInfo().getPicture() }}" alt="Logo d'équipe" class="logo"> <img src="{{ team.getInfo().getPicture() }}" alt="Logo d'équipe" class="logo">
</div> </div>
<div> <div id="colors">
<div class="color"><p>Couleur principale : </p> <div class="color"><p>Couleur principale : </p>
<div class="square" id="main_color"></div> <div class="square" id="main_color"></div>
</div> </div>
@ -75,17 +84,26 @@
<div class="square" id="second_color"></div> <div class="square" id="second_color"></div>
</div> </div>
</div> </div>
{% if isCoach %}
<button id="delete" onclick="confirm('Êtes-vous sûr de supprimer cette équipe?') ? window.location.href = '{{ path("/team/#{team.getInfo().getId()}/delete") }}' : {}">Supprimer</button>
<button></button>
{% endif %}
{% for m in team.listMembers() %} {% for m in team.listMembers() %}
<p> {{ m.getUserId() }} </p> <div class="player">
{% if m.getRole().isCoach() %} <p> {{ m.getUserId() }} </p>
{% if m.getRole().isCoach() %}
<p> : Coach</p> <p> : Coach</p>
{% else %} {% else %}
<p> : Joueur</p> <p> : Joueur</p>
{% endif %} {% endif %}
</div>
{% endfor %} {% endfor %}
</div> </div>
{% else %}
<div>
<h3>Cette équipe ne peut être affichée</h3>
</div>
{% endif %}
</section> </section>
</body> </body>
</html> </html>

@ -3,9 +3,37 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Twig view</title> <title>Twig view</title>
<style>
body {
display: flex;
flex-direction: column;
background-color: #f1f1f1;
align-items: center;
}
section{
flex-direction: row;
justify-content: space-around;
background-color: white;
width: 60%;
}
.team {
border-radius: 10px;
border-color: darkgrey;
}
.logo_team {
width: 15%;
aspect-ratio: 3/2;
object-fit: contain;
}
</style>
</head> </head>
<body> <body>
<header>
<h1><a href="{{ path('/') }}">IQBall</a></h1>
</header>
<section>
{% if teams is empty %} {% if teams is empty %}
<p>Aucune équipe n'a été trouvée</p> <p>Aucune équipe n'a été trouvée</p>
<div class="container"> <div class="container">
@ -22,12 +50,12 @@
</div> </div>
{% else %} {% else %}
{% for t in teams %} {% for t in teams %}
<div class="team" onclick="window.location.href = '{{ path("/team/#{t.id}") }}'"> <div class="team" onclick="window.location.href = '{{ path("/team/#{t.getId()}") }}'">
<p>Nom de l'équipe : {{ t.name }}</p> <p>Nom de l'équipe : {{ t.getName() }}</p>
<img src="{{ t.picture }}" alt="logo de l'équipe"> <img src="{{ t.getPicture() }}" alt="logo de l'équipe" class="logo_team">
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</section>
</body> </body>
</html> </html>

@ -0,0 +1,81 @@
<!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: 5px 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>Modifier votre équipe</h2>
<form action="{{ path('/team/' ~ team.getInfo().getId() ~ '/edit') }}" method="post">
<div class="form-group">
<label for="name">Nom de l'équipe :</label>
<input type="text" id="name" name="name" value="{{ team.getInfo().getName() }}" required>
<label for="picture">Logo:</label>
<input type="text" id="picture" name="picture" value="{{ team.getInfo().getPicture() }}" required>
<label for="main_color">Couleur principale</label>
<input type="color" value="{{ team.getInfo().getMainColor() }}" id="main_color" name="main_color" required>
<label for="second_color">Couleur secondaire</label>
<input type="color" id="second_color" name="second_color" value="{{ team.getInfo().getSecondColor() }}" required>
</div>
<div class="form-group">
<input type="submit" value="Confirmer">
</div>
</form>
</div>
</body>
</html>

@ -74,7 +74,7 @@
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% else %}
<p>Aucune équipe créé !</p> <p>Aucune équipe créée !</p>
{% endif %} {% endif %}
<h2> Mes strategies </h2> <h2> Mes strategies </h2>
@ -90,7 +90,7 @@
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% else %}
<p> Aucune tactique créé !</p> <p> Aucune tactique créée !</p>
{% endif %} {% endif %}
</body> </body>

@ -54,7 +54,6 @@
background-color: #0056b3; background-color: #0056b3;
} }
</style> </style>
</head> </head>
<body> <body>
@ -68,7 +67,7 @@
<label for="picture">Logo:</label> <label for="picture">Logo:</label>
<input type="text" id="picture" name="picture" required> <input type="text" id="picture" name="picture" required>
<label for="main_color">Couleur principale</label> <label for="main_color">Couleur principale</label>
<input type="color" id="main_color" name="main_color" required> <input type="color" value="#ffffff" id="main_color" name="main_color" required>
<label for="second_color">Couleur secondaire</label> <label for="second_color">Couleur secondaire</label>
<input type="color" id="second_color" name="second_color" required> <input type="color" id="second_color" name="second_color" required>
</div> </div>

@ -7,6 +7,9 @@
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
background-color: #f1f1f1; background-color: #f1f1f1;
display: flex;
flex-direction: column;
align-items: center;
} }
.container { .container {
@ -56,7 +59,9 @@
</style> </style>
</head> </head>
<body> <body>
<header>
<h1><a href="{{ path('/') }}">IQBall</a></h1>
</header>
<div class="container"> <div class="container">
<h2>Chercher une équipe</h2> <h2>Chercher une équipe</h2>
<form action="{{ path('/team/search') }}" method="post"> <form action="{{ path('/team/search') }}" method="post">

@ -8,54 +8,34 @@ namespace IQBall\Core\Data;
* to share to other users, or non-needed public information * to share to other users, or non-needed public information
*/ */
class Account { class Account {
/**
* @var string $email account's mail address
*/
private string $email;
/** /**
* @var string string token * @var string string token
*/ */
private string $token; private string $token;
/** /**
* @var string the account's username * @var User contains all the account's "public" information
*/ */
private string $name; private User $user;
/**
* @var int
*/
private int $id;
/** /**
* @param string $email
* @param string $name
* @param string $token * @param string $token
* @param int $id * @param User $user
*/ */
public function __construct(string $email, string $name, string $token, int $id) { public function __construct(string $token, User $user) {
$this->email = $email;
$this->name = $name;
$this->token = $token; $this->token = $token;
$this->id = $id; $this->user = $user;
}
public function getId(): int {
return $this->id;
}
public function getEmail(): string {
return $this->email;
} }
public function getToken(): string { public function getToken(): string {
return $this->token; return $this->token;
} }
public function getName(): string { /**
return $this->name; * @return User
*/
public function getUser(): User {
return $this->user;
} }
} }

@ -1,44 +0,0 @@
<?php
namespace IQBall\Core\Data;
use InvalidArgumentException;
class Color {
/**
* @var string that represents an hexadecimal color code
*/
private string $hex;
/**
* @param string $value 6 bytes unsigned int that represents an RGB color
* @throws InvalidArgumentException if the value is negative or greater than 0xFFFFFF
*/
private function __construct(string $value) {
$this->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);
}
}

@ -5,11 +5,8 @@ namespace IQBall\Core\Data;
/** /**
* information about a team member * information about a team member
*/ */
class Member { class Member implements \JsonSerializable {
/** private User $user;
* @var int The member's user account
*/
private int $userId;
/** /**
* @var int The member's team id * @var int The member's team id
@ -17,32 +14,25 @@ class Member {
private int $teamId; 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 User $user
* @param MemberRole $role * @param int $teamId
* @param string $role
*/ */
public function __construct(int $userId, int $teamId, MemberRole $role) { public function __construct(User $user, int $teamId, string $role) {
$this->userId = $userId; $this->user = $user;
$this->teamId = $teamId; $this->teamId = $teamId;
$this->role = $role; $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; return $this->role;
} }
@ -52,4 +42,16 @@ class Member {
public function getTeamId(): int { public function getTeamId(): int {
return $this->teamId; return $this->teamId;
} }
/**
* @return User
*/
public function getUser(): User {
return $this->user;
}
public function jsonSerialize() {
return get_object_vars($this);
}
} }

@ -1,68 +0,0 @@
<?php
namespace IQBall\Core\Data;
use InvalidArgumentException;
/**
* Enumeration class workaround
* As there is no enumerations in php 7.4, this class
* encapsulates an integer value and use it as a variant discriminant
*/
final class MemberRole {
private const ROLE_PLAYER = 0;
private const ROLE_COACH = 1;
private const MIN = self::ROLE_PLAYER;
private const MAX = self::ROLE_COACH;
private int $value;
private function __construct(int $val) {
if (!$this->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);
}
}

@ -2,7 +2,7 @@
namespace IQBall\Core\Data; namespace IQBall\Core\Data;
class Team { class Team implements \JsonSerializable {
private TeamInfo $info; private TeamInfo $info;
/** /**
@ -29,4 +29,10 @@ class Team {
public function listMembers(): array { public function listMembers(): array {
return $this->members; return $this->members;
} }
public function jsonSerialize() {
return get_object_vars($this);
}
} }

@ -2,21 +2,21 @@
namespace IQBall\Core\Data; namespace IQBall\Core\Data;
class TeamInfo { class TeamInfo implements \JsonSerializable {
private int $id; private int $id;
private string $name; private string $name;
private string $picture; private string $picture;
private Color $mainColor; private string $mainColor;
private Color $secondColor; private string $secondColor;
/** /**
* @param int $id * @param int $id
* @param string $name * @param string $name
* @param string $picture * @param string $picture
* @param Color $mainColor * @param string $mainColor
* @param Color $secondColor * @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->id = $id;
$this->name = $name; $this->name = $name;
$this->picture = $picture; $this->picture = $picture;
@ -37,13 +37,17 @@ class TeamInfo {
return $this->picture; return $this->picture;
} }
public function getMainColor(): Color { public function getMainColor(): string {
return $this->mainColor; return $this->mainColor;
} }
public function getSecondColor(): Color { public function getSecondColor(): string {
return $this->secondColor; return $this->secondColor;
} }
public function jsonSerialize() {
return get_object_vars($this);
}
} }

@ -0,0 +1,72 @@
<?php
namespace IQBall\Core\Data;
use _PHPStan_4c4f22f13\Nette\Utils\Json;
class User implements \JsonSerializable {
/**
* @var string $email user's mail address
*/
private string $email;
/**
* @var string the user's username
*/
private string $name;
/**
* @var int the user's id
*/
private int $id;
/**
* @var string user's profile picture
*/
private string $profilePicture;
/**
* @param string $email
* @param string $name
* @param int $id
* @param string $profilePicture
*/
public function __construct(string $email, string $name, int $id, string $profilePicture) {
$this->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);
}
}

@ -4,6 +4,7 @@ namespace IQBall\Core\Gateway;
use IQBall\Core\Connection; use IQBall\Core\Connection;
use IQBall\Core\Data\Account; use IQBall\Core\Data\Account;
use IQBall\Core\Data\User;
use PDO; use PDO;
class AccountGateway { class AccountGateway {
@ -16,13 +17,13 @@ class AccountGateway {
$this->con = $con; $this->con = $con;
} }
public function insertAccount(string $name, string $email, string $token, string $hash, string $profilePicture): int {
public function insertAccount(string $name, string $email, string $token, string $hash): int { $this->con->exec("INSERT INTO Account(username, hash, email, token,profilePicture) VALUES (:username,:hash,:email,:token,:profilePic)", [
$this->con->exec("INSERT INTO Account(username, hash, email, token) VALUES (:username,:hash,:email,:token)", [
':username' => [$name, PDO::PARAM_STR], ':username' => [$name, PDO::PARAM_STR],
':hash' => [$hash, PDO::PARAM_STR], ':hash' => [$hash, PDO::PARAM_STR],
':email' => [$email, PDO::PARAM_STR], ':email' => [$email, PDO::PARAM_STR],
':token' => [$token, PDO::PARAM_STR], ':token' => [$token, PDO::PARAM_STR],
':profilePic' => [$profilePicture, PDO::PARAM_STR],
]); ]);
return intval($this->con->lastInsertId()); return intval($this->con->lastInsertId());
} }
@ -65,7 +66,7 @@ class AccountGateway {
return null; 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 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"]));
} }

@ -4,7 +4,7 @@ namespace IQBall\Core\Gateway;
use IQBall\Core\Connection; use IQBall\Core\Connection;
use IQBall\Core\Data\Member; use IQBall\Core\Data\Member;
use IQBall\Core\Data\MemberRole; use IQBall\Core\Data\User;
use PDO; use PDO;
class MemberGateway { class MemberGateway {
@ -41,13 +41,12 @@ class MemberGateway {
*/ */
public function getMembersOfTeam(int $teamId): array { public function getMembersOfTeam(int $teamId): array {
$rows = $this->con->fetch( $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], ":id" => [$teamId, PDO::PARAM_INT],
] ]
); );
return array_map(fn($row) => new Member(new User($row['email'], $row['username'], $row['id'], $row['profilePicture']), $teamId, $row['role']), $rows);
return array_map(fn($row) => new Member($row['id_user'], $row['id_team'], MemberRole::fromName($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);
}
} }

@ -3,7 +3,6 @@
namespace IQBall\Core\Gateway; namespace IQBall\Core\Gateway;
use IQBall\Core\Connection; use IQBall\Core\Connection;
use IQBall\Core\Data\Color;
use IQBall\Core\Data\TeamInfo; use IQBall\Core\Data\TeamInfo;
use PDO; use PDO;
@ -23,7 +22,7 @@ class TeamGateway {
*/ */
public function insert(string $name, string $picture, string $mainColor, string $secondColor): int { public function insert(string $name, string $picture, string $mainColor, string $secondColor): int {
$this->con->exec( $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], ":team_name" => [$name, PDO::PARAM_STR],
":picture" => [$picture, PDO::PARAM_STR], ":picture" => [$picture, PDO::PARAM_STR],
@ -34,38 +33,37 @@ class TeamGateway {
return intval($this->con->lastInsertId()); return intval($this->con->lastInsertId());
} }
/** /**
* @param string $name * @param string $name
* @param int $id
* @return TeamInfo[] * @return TeamInfo[]
*/ */
public function listByName(string $name): array { public function listByName(string $name, int $id): array {
$result = $this->con->fetch( $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], ":name" => [$name, PDO::PARAM_STR],
"id" => [$id, PDO::PARAM_INT],
] ]
); );
return array_map(fn($row) => new TeamInfo($row['id'], $row['name'], $row['picture'], $row['main_color'], $row['second_color']), $result);
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 * @param int $id
* @return TeamInfo * @return TeamInfo|null
*/ */
public function getTeamById(int $id): ?TeamInfo { public function getTeamById(int $id): ?TeamInfo {
$row = $this->con->fetch( $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; )[0] ?? null;
if ($row == null) { if ($row == null) {
return null; return null;
} }
return new TeamInfo($row['id'], $row['name'], $row['picture'], $row['main_color'], $row['second_color']);
return new TeamInfo($row['id'], $row['name'], $row['picture'], Color::from($row['main_color']), Color::from($row['second_color']));
} }
/** /**
@ -74,16 +72,60 @@ class TeamGateway {
*/ */
public function getTeamIdByName(string $name): ?int { public function getTeamIdByName(string $name): ?int {
return $this->con->fetch( 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; )[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 * Get all the user's teams
*
* @param integer $user * @param integer $user
* @return array<array<string, mixed>> * @return array<array<string, mixed>>
*/ */

@ -2,13 +2,16 @@
namespace IQBall\Core\Model; namespace IQBall\Core\Model;
use Exception;
use IQBall\Core\Data\Account; use IQBall\Core\Data\Account;
use IQBall\Core\Data\User;
use IQBall\Core\Gateway\AccountGateway; use IQBall\Core\Gateway\AccountGateway;
use IQBall\Core\Validation\FieldValidationFail; use IQBall\Core\Validation\FieldValidationFail;
use IQBall\Core\Validation\ValidationFail; use IQBall\Core\Validation\ValidationFail;
class AuthModel { class AuthModel {
private AccountGateway $gateway; 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 * @param AccountGateway $gateway
@ -17,7 +20,6 @@ class AuthModel {
$this->gateway = $gateway; $this->gateway = $gateway;
} }
/** /**
* @param string $username * @param string $username
* @param string $password * @param string $password
@ -25,6 +27,7 @@ class AuthModel {
* @param string $email * @param string $email
* @param ValidationFail[] $failures * @param ValidationFail[] $failures
* @return Account|null the registered account or null if failures occurred * @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 { 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); $hash = password_hash($password, PASSWORD_DEFAULT);
$token = $this->generateToken(); $token = $this->generateToken();
$accountId = $this->gateway->insertAccount($username, $email, $token, $hash); $accountId = $this->gateway->insertAccount($username, $email, $token, $hash, self::DEFAULT_PROFILE_PICTURE);
return new Account($email, $username, $token, $accountId); return new Account($token, new User($email, $username, $accountId, self::DEFAULT_PROFILE_PICTURE));
} }
/** /**
* Generate a random base 64 string * Generate a random base 64 string
* @return string * @return string
* @throws Exception
*/ */
private function generateToken(): string { private function generateToken(): string {
return base64_encode(random_bytes(64)); return base64_encode(random_bytes(64));
@ -70,5 +74,4 @@ class AuthModel {
return $this->gateway->getAccountFromMail($email); return $this->gateway->getAccountFromMail($email);
} }
} }

@ -2,7 +2,6 @@
namespace IQBall\Core\Model; namespace IQBall\Core\Model;
use IQBall\Core\Data\Color;
use IQBall\Core\Data\Team; use IQBall\Core\Data\Team;
use IQBall\Core\Data\TeamInfo; use IQBall\Core\Data\TeamInfo;
use IQBall\Core\Gateway\AccountGateway; use IQBall\Core\Gateway\AccountGateway;
@ -26,6 +25,7 @@ class TeamModel {
} }
/** /**
* Create a team
* @param string $name * @param string $name
* @param string $picture * @param string $picture
* @param string $mainColor * @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 string $mail
* @param int $teamId * @param int $teamId
* @param string $role * @param string $role
* @return void * @return int
*/ */
public function addMember(string $mail, int $teamId, string $role): void { public function addMember(string $mail, int $teamId, string $role): int {
$userId = $this->users->getAccountFromMail($mail)->getId(); $user = $this->users->getAccountFromMail($mail);
$this->members->insert($teamId, $userId, $role); 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 string $name
* @param int $id
* @return TeamInfo[] * @return TeamInfo[]
*/ */
public function listByName(string $name): array { public function listByName(string $name, int $id): array {
return $this->teams->listByName($name); return $this->teams->listByName($name, $id);
} }
/** /**
* @param int $id * @param int $idTeam
* @return Team * @param int $idCurrentUser
* @return Team|null
*/ */
public function getTeam(int $id): Team { public function getTeam(int $idTeam, int $idCurrentUser): ?Team {
$teamInfo = $this->teams->getTeamById($id); if(!$this->members->isMemberOfTeam($idTeam, $idCurrentUser)) {
$members = $this->members->getMembersOfTeam($id); return null;
}
$teamInfo = $this->teams->getTeamById($idTeam);
$members = $this->members->getMembersOfTeam($idTeam);
return new Team($teamInfo, $members); return new Team($teamInfo, $members);
} }
/** /**
* delete a member from given team identifier * delete a member from given team identifier
* @param string $mail * @param int $idMember
* @param int $teamId * @param int $teamId
* @return int * @return int
*/ */
public function deleteMember(string $mail, int $teamId): int { public function deleteMember(int $idMember, int $teamId): int {
$userId = $this->users->getAccountFromMail($mail)->getId(); $this->members->remove($teamId, $idMember);
$this->members->remove($teamId, $userId); if(empty($this->members->getMembersOfTeam($teamId))) {
$this->teams->deleteTeam($teamId);
return -1;
}
return $teamId; 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 * Get all user's teams
* *
@ -88,5 +140,4 @@ class TeamModel {
public function getAll(int $user): array { public function getAll(int $user): array {
return $this->teams->getAll($user); return $this->teams->getAll($user);
} }
} }

Loading…
Cancel
Save