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"
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 && (

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

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

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

@ -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 { Segment } from "../components/arrows/BendableArrow"
import { Pos } from "../../components/arrows/Pos"
import { Segment } from "../../components/arrows/BendableArrow"
import { PlayerId } from "./Player"
export enum ActionKind {

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

@ -1,5 +1,5 @@
import { Player } from "./Player"
import { CourtObject } from "./CourtObjects"
import { CourtObject } from "./Ball"
import { Action } from "./Action"
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 { 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<RackedCourtObject[]>(
@ -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 <BallPiece />
}
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) =>

@ -162,7 +162,7 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) {
function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) {
let data
if (allTactics.length == 0) {
data = <p>Aucune tactique créé !</p>
data = <p>Aucune tactique créée !</p>
} else {
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 { RefObject } from "react"
import { MoveToHead, ScreenHead } from "../../components/actions/ArrowAction"

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

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

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

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

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

@ -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<string, mixed> $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<string, mixed> $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<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()],
]);
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);
}
}

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

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

@ -67,28 +67,43 @@
display: flex;
justify-content: space-between;
}
.failed{
color: red;
}
</style>
</head>
<body>
<header>
<h1><a href="{{ path('/') }}">IQBall</a></h1>
</header>
<div class="container">
<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">
<label for="team">Team où ajouter le membre :</label>
<input type="text" id="team" name="team" required>
<label for="mail">Email du membre :</label>
<input type="text" id="mail" name="mail" required>
<label for="email">Email du membre :</label>
{% if badEmail %}
<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">
<legend>Rôle du membre dans l'équipe :</legend>
<div class="radio">
<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 class="radio">
<label for="C">Coach</label>
<input type="radio" id="C" name="role" value="C" />
<input type="radio" id="C" name="role" value="COACH" />
</div>
</fieldset>

@ -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;
}
</style>
</head>
<body>
@ -61,13 +65,18 @@
</header>
<section class="container">
<div class="team container">
{% if notDeleted %}
<popup>
<p>Cette équipe ne peut être supprimée.</p>
</popup>
{% endif %}
{% if team is defined %}
<div class="team">
<div>
<h1>{{ team.getInfo().getName() }}</h1>
<img src="{{ team.getInfo().getPicture() }}" alt="Logo d'équipe" class="logo">
</div>
<div>
<div id="colors">
<div class="color"><p>Couleur principale : </p>
<div class="square" id="main_color"></div>
</div>
@ -75,17 +84,26 @@
<div class="square" id="second_color"></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() %}
<p> {{ m.getUserId() }} </p>
{% if m.getRole().isCoach() %}
<div class="player">
<p> {{ m.getUserId() }} </p>
{% if m.getRole().isCoach() %}
<p> : Coach</p>
{% else %}
{% else %}
<p> : Joueur</p>
{% endif %}
{% endif %}
</div>
{% endfor %}
</div>
{% else %}
<div>
<h3>Cette équipe ne peut être affichée</h3>
</div>
{% endif %}
</section>
</body>
</html>

@ -3,9 +3,37 @@
<head>
<meta charset="UTF-8">
<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>
<body>
<header>
<h1><a href="{{ path('/') }}">IQBall</a></h1>
</header>
<section>
{% if teams is empty %}
<p>Aucune équipe n'a été trouvée</p>
<div class="container">
@ -22,12 +50,12 @@
</div>
{% else %}
{% for t in teams %}
<div class="team" onclick="window.location.href = '{{ path("/team/#{t.id}") }}'">
<p>Nom de l'équipe : {{ t.name }}</p>
<img src="{{ t.picture }}" alt="logo de l'équipe">
<div class="team" onclick="window.location.href = '{{ path("/team/#{t.getId()}") }}'">
<p>Nom de l'équipe : {{ t.getName() }}</p>
<img src="{{ t.getPicture() }}" alt="logo de l'équipe" class="logo_team">
</div>
{% endfor %}
{% endif %}
</section>
</body>
</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>
{% endfor %}
{% else %}
<p>Aucune équipe créé !</p>
<p>Aucune équipe créée !</p>
{% endif %}
<h2> Mes strategies </h2>
@ -90,7 +90,7 @@
</div>
{% endfor %}
{% else %}
<p> Aucune tactique créé !</p>
<p> Aucune tactique créée !</p>
{% endif %}
</body>

@ -54,7 +54,6 @@
background-color: #0056b3;
}
</style>
</head>
<body>
@ -68,7 +67,7 @@
<label for="picture">Logo:</label>
<input type="text" id="picture" name="picture" required>
<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>
<input type="color" id="second_color" name="second_color" required>
</div>

@ -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 @@
</style>
</head>
<body>
<header>
<h1><a href="{{ path('/') }}">IQBall</a></h1>
</header>
<div class="container">
<h2>Chercher une équipe</h2>
<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
*/
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;
}
}

@ -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
*/
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);
}
}

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

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

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

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

@ -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<array<string, mixed>>
*/

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

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

Loading…
Cancel
Save