Compare commits

...

11 Commits

Author SHA1 Message Date
Vivien DUFOUR 2ace144c6a fix conflicts
continuous-integration/drone/push Build is failing Details
1 year ago
Vivien DUFOUR c383432b64 fix ./verify
continuous-integration/drone/push Build is passing Details
1 year ago
Vivien DUFOUR 8315b79ba1 can now share tactic with account
continuous-integration/drone/push Build is failing Details
1 year ago
Vivien DUFOUR bd244c8dde push all my shareTactic work since 2 weeks, (can share and unshare tactic to team, draggable element for share, share icon on tactic hover to share to an account, just need to fix api tomorrow)
continuous-integration/drone/push Build is failing Details
1 year ago
Vivien DUFOUR e962c17f4d can drag tactic to a team to share and unshare tactic on team panel
continuous-integration/drone/push Build is failing Details
1 year ago
Vivien DUFOUR 588f580bf9 Can now share, unshare and open shared tactic
continuous-integration/drone/push Build is failing Details
1 year ago
Vivien DUFOUR 86a25d18b2 fix home-page (need to fix unauthorized access to shared tactic)
continuous-integration/drone/push Build is failing Details
1 year ago
Vivien DUFOUR 51f150a16a can share tactic to team or account and can unshare tactic to everyone (build will fail because need to modify home page)
continuous-integration/drone/push Build is failing Details
1 year ago
Vivien DUFOUR 2beda1c7f5 can share tactic to team (test)
continuous-integration/drone/push Build is failing Details
1 year ago
Vivien DUFOUR fa81bd0702 conflicts
1 year ago
maxime.batista 41d431c1cb fixes
1 year ago

@ -3,7 +3,6 @@
object Account {
<u>id
name
age
email
phone_number
password_hash
@ -24,6 +23,9 @@ object Tactic {
<u>id
name
creationDate
owner
content
courtType
}
object Team {

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

@ -22,6 +22,7 @@ body {
margin: 0px;
height: 100%;
background-color: var(--second-color);
overflow: hidden;
}
.data {
@ -29,6 +30,7 @@ body {
background-color: var(--main-color);
border-radius: 0.75cap;
color: var(--main-contrast-color);
position: relative;
}
.data:hover {

@ -23,7 +23,6 @@
border-collapse: separate;
border-spacing: 1em;
table-layout: fixed;
overflow: hidden;
}
#body-personal-space td {
@ -38,3 +37,25 @@
tbody p {
text-align: center;
}
.share-icon-container {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
padding: 5px;
}
.share-button {
cursor: pointer;
}
.share-icon-button {
background-color: transparent;
border: none;
}
.share-icon {
color: white;
font-size: 2em;
}

@ -16,6 +16,7 @@
#side-menu-content {
width: 90%;
}
.titre-side-menu {
border-bottom: var(--main-color) solid 3px;
width: 100%;

@ -1,22 +1,68 @@
@import url(../style/theme/dark.css);
@import url(../style/template/header.css);
body {
margin: 0;
background-color: var(--second-color);
color: white;
}
#main-div {
display: flex;
flex-direction: column;
align-items: stretch;
font-family: var(--font-content);
height: 100vh;
}
#content-container {
display: flex;
flex-direction: row;
align-items: stretch;
height: 90vh;
margin: 10px 2% 0 2%;
}
#left-panel {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
width: 68%;
margin-right: 2%;
margin-bottom: 15px;
}
#main-div header {
#right-panel {
display: flex;
flex-direction: column;
align-items: center;
width: 30%;
flex-grow: 1;
}
header {
display: flex;
justify-content: center;
background-color: #525252;
background-color: var(--main-color);
width: 100%;
margin-bottom: 5px;
height: 80%;
padding-bottom: 10px;
padding-top: 10px;
}
header h1 a {
color: orange;
color: var(--accent-color);
text-decoration: none;
font-size: 1.5em;
font-size: 1.4em;
}
html,
body,
#main-div,
#content-container,
#right-panel,
#tactics {
height: 100%;
}
.square {
@ -29,10 +75,10 @@ header h1 a {
display: flex;
flex-direction: column;
align-items: center;
width: 60%;
background-color: #8f8f8f;
width: 100%;
padding-bottom: 10px;
border-radius: 10px;
background-color: var(--third-color);
}
#first-part {
@ -45,10 +91,19 @@ header h1 a {
font-size: 2.8em;
}
#logo {
aspect-ratio: 3/2;
object-fit: contain;
max-width: 60%; /* Adjusted max-width for the team logo */
max-height: 60vh; /* Added max-height for the team logo */
margin-bottom: 10px; /* Added margin at the bottom */
}
#colors {
display: flex;
flex-direction: column;
}
.color {
flex-direction: row;
justify-content: space-between;
@ -69,13 +124,6 @@ header h1 a {
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;
@ -98,6 +146,7 @@ header h1 a {
display: flex;
flex-direction: row;
justify-content: space-evenly;
margin-bottom: 10px;
}
#add-member {
@ -110,8 +159,8 @@ header h1 a {
#members {
display: flex;
flex-direction: column;
background-color: #bcbcbc;
width: 60%;
background-color: var(--third-color);
width: 100%;
align-items: center;
justify-content: space-around;
border-radius: 10px;
@ -119,7 +168,7 @@ header h1 a {
.member {
width: 60%;
background-color: white;
background-color: #494b5d;
display: flex;
flex-direction: row;
justify-content: space-evenly;
@ -133,3 +182,54 @@ header h1 a {
height: 40px;
width: 40px;
}
#tactics {
display: flex;
flex-direction: column;
align-items: center;
background-color: var(--third-color);
width: 100%;
height: 100%;
border-radius: 10px;
overflow: auto;
margin-bottom: 5px;
}
#head-tactics {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-evenly;
margin-bottom: 10px;
}
.tactic {
box-sizing: border-box;
width: 100%;
background-color: #494b5d;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
border-radius: 10px;
margin-top: 5px;
margin-bottom: 5px;
padding: 10px;
max-width: 90%;
cursor: pointer;
}
.tactic button#open-tactic {
border-radius: 10px;
background-color: green;
color: white;
padding: 5px 8px;
}
.tactic button#delete {
border-radius: 10px;
background-color: red;
color: white;
padding: 5px 8px;
margin-left: 10px;
}

@ -1,8 +1,15 @@
import "../style/home/home.css"
// import AccountSvg from "../assets/account.svg?react"
import { Header } from "./template/Header"
import { BASE } from "../Constants"
import Draggable from "react-draggable"
import { NULL_POS } from "../components/arrows/Pos"
import { contains } from "../components/arrows/Box"
import React, { useRef, useState } from "react"
import { fetchAPI } from "../Fetcher"
import { User } from "../model/User"
import { FaShare } from "react-icons/fa"
import { SaveStates } from "../components/editor/SavingState"
interface Tactic {
id: number
@ -22,20 +29,21 @@ export default function Home({
lastTactics,
allTactics,
teams,
username,
user,
}: {
lastTactics: Tactic[]
allTactics: Tactic[]
teams: Team[]
username: string
user: User
}) {
return (
<div id="main">
<Header username={username} />
<Header user={user} />
<Body
lastTactics={lastTactics}
allTactics={allTactics}
teams={teams}
user={user}
/>
</div>
)
@ -45,16 +53,23 @@ function Body({
lastTactics,
allTactics,
teams,
user,
}: {
lastTactics: Tactic[]
allTactics: Tactic[]
teams: Team[]
user: User
}) {
const widthPersonalSpace = 78
const widthSideMenu = 100 - widthPersonalSpace
return (
<div id="body">
<PersonalSpace width={widthPersonalSpace} allTactics={allTactics} />
<PersonalSpace
width={widthPersonalSpace}
allTactics={allTactics}
teams={teams}
user={user}
/>
<SideMenu
width={widthSideMenu}
lastTactics={lastTactics}
@ -80,8 +95,8 @@ function SideMenu({
width: width + "%",
}}>
<div id="side-menu-content">
<Team teams={teams} />
<Tactic lastTactics={lastTactics} />
<TeamList teams={teams} />
<TacticList lastTactics={lastTactics} />
</div>
</div>
)
@ -90,9 +105,13 @@ function SideMenu({
function PersonalSpace({
width,
allTactics,
teams,
user,
}: {
width: number
allTactics: Tactic[]
teams: Team[]
user: User
}) {
return (
<div
@ -101,7 +120,11 @@ function PersonalSpace({
width: width + "%",
}}>
<TitlePersonalSpace />
<BodyPersonalSpace allTactics={allTactics} />
<BodyPersonalSpace
allTactics={allTactics}
teams={teams}
user={user}
/>
</div>
)
}
@ -114,7 +137,15 @@ function TitlePersonalSpace() {
)
}
function TableData({ allTactics }: { allTactics: Tactic[] }) {
function TableData({
allTactics,
teams,
user,
}: {
allTactics: Tactic[]
teams: Team[]
user: User
}) {
const nbRow = Math.floor(allTactics.length / 3) + 1
let listTactic = Array(nbRow)
for (let i = 0; i < nbRow; i++) {
@ -133,15 +164,12 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) {
i = 0
while (i < nbRow) {
listTactic[i] = listTactic[i].map((tactic: Tactic) => (
<td
key={tactic.id}
className="data"
onClick={() => {
location.pathname = BASE + "/tactic/" + tactic.id + "/edit"
}}>
{truncateString(tactic.name, 25)}
</td>
listTactic[i] = listTactic[i].map((tactic: Tactic, index: number) => (
<DraggableTableDataElement
key={index}
tactic={tactic}
teams={teams}
/>
))
i++
}
@ -159,12 +187,90 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) {
return data
}
function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) {
function DraggableTableDataElement({
tactic,
teams,
}: {
tactic: Tactic
teams: Team[]
}) {
const ref = useRef<HTMLDivElement>(null)
const [dragging, setDragging] = useState(false)
const [hovered, setHovered] = useState(false)
const handleButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation()
if (!dragging) {
const userEmail = window.prompt(
"Entrez l'email à qui partager la tactique :",
)
if (userEmail != null) {
onShareTactic(userEmail, tactic)
}
} else {
setDragging(false)
}
}
return (
<Draggable
position={NULL_POS}
nodeRef={ref}
onDrag={() => setDragging(true)}
onStop={() => {
if (dragging) {
if (ref.current) {
onDropTactic(
ref.current.getBoundingClientRect(),
tactic,
teams,
)
}
}
}}>
<td
key={tactic.id}
ref={ref as React.RefObject<HTMLTableDataCellElement>}
className="data"
onClick={() => {
if (!dragging) {
location.pathname =
BASE + "/tactic/" + tactic.id + "/edit"
} else {
setDragging(false)
}
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}>
{truncateString(tactic.name, 25)}
{hovered && (
<div className="share-icon-container">
<button
className="share-button share-icon-button"
onClick={handleButtonClick}>
<FaShare className="share-icon" />
</button>
</div>
)}
</td>
</Draggable>
)
}
function BodyPersonalSpace({
allTactics,
teams,
user,
}: {
allTactics: Tactic[]
teams: Team[]
user: User
}) {
let data
if (allTactics.length == 0) {
data = <p>Aucune tactique créée !</p>
} else {
data = <TableData allTactics={allTactics} />
data = <TableData allTactics={allTactics} teams={teams} user={user} />
}
return (
@ -176,7 +282,7 @@ function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) {
)
}
function Team({ teams }: { teams: Team[] }) {
function TeamList({ teams }: { teams: Team[] }) {
return (
<div id="teams">
<div className="titre-side-menu">
@ -192,7 +298,7 @@ function Team({ teams }: { teams: Team[] }) {
)
}
function Tactic({ lastTactics }: { lastTactics: Tactic[] }) {
function TacticList({ lastTactics }: { lastTactics: Tactic[] }) {
return (
<div id="tactic">
<div className="titre-side-menu">
@ -210,14 +316,16 @@ function Tactic({ lastTactics }: { lastTactics: Tactic[] }) {
}
function SetButtonTactic({ tactics }: { tactics: Tactic[] }) {
const lastTactics = tactics.map((tactic) => (
<ButtonLastTactic tactic={tactic} />
const lastTactics = tactics.map((tactic, i) => (
<ButtonLastTactic key={i} tactic={tactic} />
))
return <div className="set-button">{lastTactics}</div>
}
function SetButtonTeam({ teams }: { teams: Team[] }) {
const listTeam = teams.map((teams) => <ButtonTeam team={teams} />)
const listTeam = teams.map((teams, i) => (
<ButtonTeam key={i} team={teams} />
))
return <div className="set-button">{listTeam}</div>
}
@ -226,7 +334,7 @@ function ButtonTeam({ team }: { team: Team }) {
return (
<div>
<div
id={"button-team" + team.id}
id={"button-team-" + team.id}
className="button-side-menu data"
onClick={() => {
location.pathname = BASE + "/team/" + team.id
@ -257,3 +365,63 @@ function truncateString(name: string, limit: number): string {
}
return name
}
function onDropTactic(ref: DOMRect, tactic: Tactic, teams: Team[]) {
let shared = false
for (const team of teams) {
if (
contains(
ref,
document
.getElementById(`button-team-${team.id}`)!
.getBoundingClientRect(),
)
) {
if (!shared) {
shareTacticToTeam(tactic, team)
shared = true
}
}
}
}
async function onShareTactic(email: string, tactic: Tactic) {
const canShareResponse = await fetchAPI(
`tactic/${tactic.id}/can-share`,
tactic,
)
if (canShareResponse.ok) {
const shareToAccountResponse = await fetchAPI(
`tactic/${tactic.id}/share-to-account`,
{ email },
)
if (!shareToAccountResponse.ok) {
alert(
"Une erreur s'est produite lors du partage de la tactique avec ce compte",
)
}
} else {
alert("Vous ne pouvez pas partager cette tactique")
}
}
async function shareTacticToTeam(tactic: Tactic, team: Team) {
const canShare = await fetchAPI(
`tactic/${tactic.id}/can-share-to-team`,
team,
).then((r) => r.ok)
if (
canShare &&
confirm(
"Etes-vous sûr de vouloir partager la tactique " +
tactic.name +
" avec l'équipe " +
team.name,
)
) {
fetchAPI(`tactic/${tactic.id}/share-to-team`, team)
}
if (!canShare) {
alert("Vous ne pouvez pas partager cette tactique à cette équipe")
}
}

@ -1,28 +1,44 @@
import "../style/team_panel.css"
import { BASE } from "../Constants"
import { Team, TeamInfo, Member } from "../model/Team"
import { User } from "../model/User"
import { Tactic } from "../model/tactic/Tactic"
import { fetchAPI } from "../Fetcher"
import { useState } from "react"
export default function TeamPanel({
isCoach,
team,
currentUserId,
tactics,
}: {
isCoach: boolean
team: Team
currentUserId: number
tactics: Tactic[]
}) {
const [teamTactics, setTeamTactics] = useState(tactics)
function handleTacticDelete(tacticId: number) {
fetchAPI(`tactic/${tacticId}/unshare-to-team`, team.info)
const updatedTactics = teamTactics.filter(
(tactic) => tactic.id !== tacticId,
)
setTeamTactics(updatedTactics)
}
return (
<div id="main-div">
<div id="header-div">
<header>
<h1>
<a href={BASE + "/"}>IQBall</a>
</h1>
</header>
</div>
<div id="content-container">
<div id="left-panel">
<TeamDisplay team={team.info} />
{isCoach && <CoachOptions id={team.info.id} />}
<MembersDisplay
members={team.members}
isCoach={isCoach}
@ -30,6 +46,17 @@ export default function TeamPanel({
currentUserId={currentUserId}
/>
</div>
<div id="right-panel">
<TacticsDisplay
tactics={teamTactics}
isCoach={isCoach}
onTacticDelete={(tacticId) =>
handleTacticDelete(tacticId)
}
/>
</div>
</div>
</div>
)
}
@ -94,6 +121,7 @@ function MembersDisplay({
}) {
const listMember = members.map((member) => (
<MemberDisplay
key={member.user.id}
member={member}
isCoach={isCoach}
idTeam={idTeam}
@ -167,3 +195,62 @@ function MemberDisplay({
</div>
)
}
function TacticsDisplay({
tactics,
isCoach,
onTacticDelete,
}: {
tactics: Tactic[]
isCoach: boolean
onTacticDelete: (tacticId: number) => void
}) {
const listTactic = tactics.map((tactic) => (
<TacticDisplay
key={tactic.id}
tactic={tactic}
isCoach={isCoach}
onTacticDelete={() => onTacticDelete(tactic.id)}
/>
))
return (
<div id="tactics">
<div id="head-tactics">
<h1>Tactiques partagées</h1>
</div>
{listTactic}
</div>
)
}
function TacticDisplay({
tactic,
isCoach,
onTacticDelete,
}: {
tactic: Tactic
isCoach: boolean
onTacticDelete: () => void
}) {
return (
<div
className="tactic"
onClick={() =>
(location.pathname = BASE + "/tactic/" + tactic.id + "/edit")
}>
<p>{tactic.name}</p>
{isCoach && (
<button
id="delete"
onClick={(event) => {
event.stopPropagation()
confirm(
"Êtes-vous sûr de vouloir supprimer le partage cette tactique ?",
) && onTacticDelete()
}}>
Retirer
</button>
)}
</div>
)
}

@ -1,11 +1,12 @@
import { BASE } from "../../Constants"
import accountSvg from "../../assets/account.svg"
import { User } from "../../model/User"
/**
*
* @param param0 username
* @returns Header
*/
export function Header({ username }: { username: string }) {
export function Header({ user }: { user: User }) {
return (
<div id="header">
<div id="header-left"></div>
@ -22,15 +23,16 @@ export function Header({ username }: { username: string }) {
</div>
<div id="header-right">
<div className="clickable" id="clickable-header-right">
{/* <AccountSvg id="img-account" /> */}
<img
id="img-account"
src={accountSvg}
src={user.profilePicture}
onClick={() => {
location.pathname = BASE + "/settings"
}}
alt="photo de profil"
/>
<p id="username">{username}</p>
<p id="username">{user.name}</p>
</div>
</div>
</div>

@ -13,6 +13,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-icons": "^5.0.1",
"typescript": "^5.2.2",
"vite": "^4.5.0",
"vite-plugin-css-injected-by-js": "^3.3.0"

@ -17,12 +17,18 @@ use IQBall\Core\Gateway\AccountGateway;
use IQBall\Core\Gateway\TacticInfoGateway;
use IQBall\Core\Model\AuthModel;
use IQBall\Core\Model\TacticModel;
use IQBall\Core\Gateway\TeamGateway;
use IQBall\Core\Model\TeamModel;
use IQBall\Core\Gateway\MemberGateway;
$basePath = get_base_path() . "/api";
function getTacticController(): APITacticController {
return new APITacticController(new TacticModel(new TacticInfoGateway(new Connection(get_database()))));
return new APITacticController(
new TacticModel(new TacticInfoGateway(new Connection(get_database())), new AccountGateway(new Connection(get_database()))),
new TeamModel(new TeamGateway(new Connection(get_database())), new MemberGateway(new Connection(get_database())), new AccountGateway(new Connection(get_database())))
);
}
function getAuthController(): APIAuthController {
@ -70,6 +76,14 @@ function getRoutes(): AltoRouter {
$router->map("POST", "/tactic/[i:id]/can-share", Action::auth(fn(int $id, Account $acc) => getTacticController()->canShareTactic($id, $acc)));
$router->map("POST", "/tactic/[i:id]/can-share-to-team", Action::auth(fn(int $id, Account $acc) => getTacticController()->canShareTacticToTeam($id, $acc)));
$router->map("POST", "/tactic/[i:id]/share-to-team", Action::auth(fn(int $id, Account $acc) => getTacticController()->shareTacticToTeam($id, $acc)));
$router->map("POST", "/tactic/[i:id]/share-to-account", Action::auth(fn(int $id, Account $acc) => getTacticController()->shareTacticToAccount($id, $acc)));
$router->map("POST", "/tactic/[i:id]/unshare-to-team", Action::auth(fn(int $id, Account $acc) => getTacticController()->unshareTacticToTeam($id, $acc)));
return $router;
}

@ -11,6 +11,7 @@ use IQBall\App\Controller\EditorController;
use IQBall\App\Controller\TeamController;
use IQBall\App\Controller\UserController;
use IQBall\App\Controller\VisualizerController;
use IQBall\App\Controller\TacticController;
use IQBall\App\Session\MutableSessionHandle;
use IQBall\App\Session\PhpSessionHandle;
use IQBall\App\Session\SessionHandle;
@ -37,15 +38,15 @@ function getConnection(): Connection {
}
function getUserController(): UserController {
return new UserController(new TacticModel(new TacticInfoGateway(getConnection())), new TeamModel(new TeamGateway(getConnection()), new MemberGateway(getConnection()), new AccountGateway(getConnection())));
return new UserController(new TacticModel(new TacticInfoGateway(getConnection()), new AccountGateway(getConnection())), new TeamModel(new TeamGateway(getConnection()), new MemberGateway(getConnection()), new AccountGateway(getConnection())));
}
function getVisualizerController(): VisualizerController {
return new VisualizerController(new TacticModel(new TacticInfoGateway(getConnection())));
return new VisualizerController(new TacticModel(new TacticInfoGateway(getConnection()), new AccountGateway(getConnection())));
}
function getEditorController(): EditorController {
return new EditorController(new TacticModel(new TacticInfoGateway(getConnection())));
return new EditorController(new TacticModel(new TacticInfoGateway(getConnection()), new AccountGateway(getConnection())));
}
function getTeamController(): TeamController {
@ -53,6 +54,11 @@ function getTeamController(): TeamController {
return new TeamController(new TeamModel(new TeamGateway($con), new MemberGateway($con), new AccountGateway($con)));
}
function getTacticController(): TacticController {
return new TacticController(new TacticModel(new TacticInfoGateway(getConnection()), new AccountGateway(getConnection())), new TeamModel(new TeamGateway(getConnection()), new MemberGateway(getConnection()), new AccountGateway(getConnection())));
}
function getAuthController(): AuthController {
return new AuthController(new AuthModel(new AccountGateway(getConnection())));
}
@ -86,6 +92,17 @@ function getRoutes(): AltoRouter {
$ar->map("GET", "/disconnect", Action::auth(fn(MutableSessionHandle $s) => getUserController()->disconnect($s)));
$ar->map("GET", "/shareTactic", Action::auth(fn(SessionHandle $s) => getTacticController()->displayTactic(true, $s)));
$ar->map("GET", "/shareTactic/[i:id]", Action::auth(fn(int $id, SessionHandle $s) => getTacticController()->displayTeamAndAccount(true, $id, $s)));
$ar->map("GET", "/shareTactic/[i:id]/team/[i:idTeam]", Action::auth(fn(int $tacticId, int $teamId, SessionHandle $s) => getTacticController()->displayShareConfirmation($tacticId, $teamId, $s)));
$ar->map("POST", "/shareTactic/[i:id]/team/[i:idTeam]", Action::auth(fn(int $tacticId, int $teamId, SessionHandle $s) => getTacticController()->shareTacticToTeam($_POST, $tacticId, $teamId, $s)));
$ar->map("POST", "/shareTactic/[i:id]/account", Action::auth(fn(int $tacticId, SessionHandle $s) => getTacticController()->shareTacticToAccount($_POST, $tacticId, $s)));
$ar->map("GET", "/unshareTactic", Action::auth(fn(SessionHandle $s) => getTacticController()->displayTactic(false, $s)));
$ar->map("GET", "/unshareTactic/[i:id]", Action::auth(fn(int $id, SessionHandle $s) => getTacticController()->displayTeamAndAccount(false, $id, $s)));
$ar->map("POST", "/unshareTactic/[i:id]", Action::auth(fn(int $id, SessionHandle $s) => getTacticController()->unshareTactic($id, $s)));
//tactic-related
$ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->openVisualizer($id, $s)));
$ar->map("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->openEditor($id, $s)));
@ -95,6 +112,7 @@ function getRoutes(): AltoRouter {
$ar->map("GET", "/tactic/new/plain", Action::noAuth(fn(SessionHandle $s) => getEditorController()->createNewOfKind(CourtType::plain(), $s)));
$ar->map("GET", "/tactic/new/half", Action::noAuth(fn(SessionHandle $s) => getEditorController()->createNewOfKind(CourtType::half(), $s)));
//team-related
$ar->map("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s)));
$ar->map("POST", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->submitTeam($_POST, $s)));
@ -108,7 +126,6 @@ function getRoutes(): AltoRouter {
$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;
}

@ -4,6 +4,8 @@ DROP TABLE IF EXISTS Tactic;
DROP TABLE IF EXISTS Team;
DROP TABLE IF EXISTS User;
DROP TABLE IF EXISTS Member;
DROP TABLE IF EXISTS TacticSharedTeam;
DROP TABLE IF EXISTS TacticSharedAccount;
CREATE TABLE Admins
(
@ -45,6 +47,25 @@ CREATE TABLE Member
id_team integer NOT NULL,
id_user integer NOT NULL,
role text CHECK (role IN ('COACH', 'PLAYER')) NOT NULL,
PRIMARY KEY(id_team, id_user),
FOREIGN KEY (id_team) REFERENCES Team (id),
FOREIGN KEY (id_user) REFERENCES Account (id)
);
CREATE TABLE TacticSharedTeam
(
id_team integer NOT NULL,
id_tactic integer NOT NULL,
PRIMARY KEY(id_team, id_tactic),
FOREIGN KEY (id_team) REFERENCES Team (id),
FOREIGN KEY (id_tactic) REFERENCES Tactic (id)
);
CREATE TABLE TacticSharedAccount
(
id_account integer NOT NULL,
id_tactic integer NOT NULL,
PRIMARY KEY(id_account, id_tactic),
FOREIGN KEY (id_account) REFERENCES Account (id),
FOREIGN KEY (id_tactic) REFERENCES Tactic (id)
);

@ -3,6 +3,7 @@
namespace IQBall\Api\Controller;
use IQBall\Api\APIControl;
use IQBall\Core\Control;
use IQBall\Core\Data\Account;
use IQBall\Core\Data\TacticInfo;
use IQBall\Core\Http\HttpCodes;
@ -10,19 +11,25 @@ use IQBall\Core\Http\HttpRequest;
use IQBall\Core\Http\HttpResponse;
use IQBall\Core\Http\JsonHttpResponse;
use IQBall\Core\Model\TacticModel;
use IQBall\Core\Validation\DefaultValidators;
use IQBall\Core\Model\TeamModel;
use IQBall\Core\Validation\FieldValidationFail;
use IQBall\Core\Validation\Validators;
/**
* API endpoint related to tactics
*/
class APITacticController {
private TacticModel $model;
private TacticModel $tacticModel;
private TeamModel $teamModel;
/**
* @param TacticModel $model
* @param TacticModel $tacticModel
* @param TeamModel $teamModel
*/
public function __construct(TacticModel $model) {
$this->model = $model;
public function __construct(TacticModel $tacticModel, TeamModel $teamModel) {
$this->tacticModel = $tacticModel;
$this->teamModel = $teamModel;
}
/**
@ -36,7 +43,7 @@ class APITacticController {
"name" => [DefaultValidators::lenBetween(1, 50), DefaultValidators::nameWithSpaces()],
], function (HttpRequest $request) use ($tactic_id, $account) {
$failures = $this->model->updateName($tactic_id, $request["name"], $account->getUser()->getId());
$failures = $this->tacticModel->updateName($tactic_id, $request["name"], $account->getUser()->getId());
if (!empty($failures)) {
//TODO find a system to handle Unauthorized error codes more easily from failures.
@ -56,8 +63,7 @@ class APITacticController {
return APIControl::runChecked([
"content" => [],
], function (HttpRequest $req) use ($id) {
//TODO verify that the account has the rights to update the tactic content
if ($fail = $this->model->updateContent($id, json_encode($req["content"]))) {
if ($fail = $this->tacticModel->updateContent($id, json_encode($req["content"]))) {
return new JsonHttpResponse([$fail], HttpCodes::BAD_REQUEST);
}
return HttpResponse::fromCode(HttpCodes::OK);
@ -65,12 +71,99 @@ class APITacticController {
}
/**
* @param int $tacticId
* @param Account $account
* @return HttpResponse
*/
public function canShareTactic(int $tacticId, Account $account): HttpResponse {
if ($this->tacticModel->canShareTactic($tacticId, $account)) {
return HttpResponse::fromCode(HttpCodes::OK);
}
return new JsonHttpResponse(["message" => "Vous ne pouvez pas partager cette tactique"], HttpCodes::FORBIDDEN);
}
/**
* @param int $tacticId
* @param Account $account
* @return HttpResponse
*/
public function canShareTacticToTeam(int $tacticId, Account $account): HttpResponse {
return Control::runChecked([
"id" => [],
"name" => [],
"picture" => [],
"main_color" => [],
"second_color" => [],
], function (HttpRequest $request) use ($tacticId, $account) {
if ($this->canShareTactic($tacticId, $account)->getCode() == HttpCodes::OK) {
if ($this->teamModel->canShareTacticToTeam($request["id"], $account->getUser()->getEmail())) {
return HttpResponse::fromCode(HttpCodes::OK);
}
}
return new JsonHttpResponse(["message" => "Action non autorisée"], HttpCodes::FORBIDDEN);
});
}
/**
* @param int $tacticId
* @param Account $account
* @return HttpResponse
*/
public function shareTacticToTeam(int $tacticId, Account $account): HttpResponse {
return Control::runChecked([
"id" => [],
"name" => [],
"picture" => [],
"main_color" => [],
"second_color" => [],
], function (HttpRequest $request) use ($tacticId) {
$this->teamModel->shareTacticToTeam($request["id"], $tacticId);
return HttpResponse::fromCode(HttpCodes::OK);
});
}
/**
* @param int $tacticId
* @param Account $account
* @return HttpResponse
*/
public function shareTacticToAccount(int $tacticId, Account $account): HttpResponse {
return Control::runChecked([
"email" => [],
], function (HttpRequest $request) use ($tacticId) {
$this->tacticModel->shareTacticToAccountMail($request["email"], $tacticId);
return HttpResponse::fromCode(HttpCodes::OK);
});
}
/**
* @param int $tacticId
* @param Account $account
* @return HttpResponse
*/
public function unshareTacticToTeam(int $tacticId, Account $account): HttpResponse {
return Control::runChecked([
"id" => [],
"name" => [],
"picture" => [],
"main_color" => [],
"second_color" => [],
], function (HttpRequest $request) use ($tacticId, $account) {
if ($this->teamModel->canShareTacticToTeam($request["id"], $account->getUser()->getEmail())) {
$this->teamModel->unshareTacticToTeam($tacticId, $request["id"]);
return HttpResponse::fromCode(HttpCodes::OK);
}
return new JsonHttpResponse(["message" => "Action non autorisée"], HttpCodes::FORBIDDEN);
});
}
/**
* @param int $userId
* @return HttpResponse given user information.
*/
public function getUserTactics(int $userId): HttpResponse {
$tactics = $this->model->listAllOf($userId);
$tactics = $this->tacticModel->listAllOf($userId);
$response = array_map(fn(TacticInfo $t) => [
'id' => $t->getId(),
@ -83,5 +176,4 @@ class APITacticController {
return new JsonHttpResponse($response);
}
}

@ -76,7 +76,15 @@ class EditorController {
public function openEditor(int $id, SessionHandle $session): ViewHttpResponse {
$tactic = $this->model->get($id);
$failure = TacticValidator::validateAccess($id, $tactic, $session->getAccount()->getUser()->getId());
$ownerId = $session->getAccount()->getUser()->getId();
$ids = $this->model->getOwnerIdTacticShared($id, $session->getAccount()->getUser()->getId());
if(isset($ids)) {
$ownerId = $ids['owner'];
}
$failure = TacticValidator::validateAccess($id, $tactic, $ownerId);
if ($failure != null) {
return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND);

@ -0,0 +1,119 @@
<?php
namespace IQBall\App\Controller;
use IQBall\App\Session\SessionHandle;
use IQBall\App\ViewHttpResponse;
use IQBall\Core\Model\TacticModel;
use IQBall\Core\Model\TeamModel;
use IQBall\Core\Http\HttpResponse;
class TacticController {
private TacticModel $tactics;
private ?TeamModel $teams;
public function __construct(TacticModel $tactics, ?TeamModel $teams = null) {
$this->tactics = $tactics;
$this->teams = $teams;
}
/**
* @param bool $toShare
* @param SessionHandle $session
* @return ViewHttpResponse
*/
public function displayTactic(bool $toShare, SessionHandle $session): ViewHttpResponse {
if($toShare) {
$results = $this->tactics->getAll($session->getAccount()->getUser()->getId());
} else {
$results = $this->tactics->getAllTacticSharedOwned($session->getAccount()->getUser()->getId());
}
return ViewHttpResponse::twig("display_tactic.html.twig", ['tactics' => $results, 'toShare' => $toShare]);
}
/**
* @param bool $toShare
* @param int $tacticId
* @param SessionHandle $session
* @return ViewHttpResponse
*/
public function displayTeamAndAccount(bool $toShare, int $tacticId, SessionHandle $session): ViewHttpResponse {
if($toShare) {
$results = $this->teams->getAllIsCoach($session->getAccount()->getUser()->getId());
} else {
$results = $this->teams->getAllIsCoach($session->getAccount()->getUser()->getId());
}
return ViewHttpResponse::twig("display_user_teams_accounts.html.twig", ['teams' => $results, 'tactic' => $tacticId, 'toShare' => $toShare]);
}
/**
* @param int $tacticId
* @param int $teamId
* @param SessionHandle $session
* @return ViewHttpResponse
*/
public function displayShareConfirmation(int $tacticId, int $teamId, SessionHandle $session): ViewHttpResponse {
return ViewHttpResponse::twig("display_share_confirmation.html.twig", ['team' => $teamId, 'tactic' => $tacticId]);
}
/**
* @param array<array<string, mixed>> $confirmation
* @param int $tacticId
* @param int $teamId
* @param SessionHandle $session
* @return HttpResponse
*/
public function shareTacticToTeam(array $confirmation, int $tacticId, int $teamId, SessionHandle $session): HttpResponse {
if($confirmation['confirmation'] == "yes") {
$this->teams->shareTacticToTeam($teamId, $tacticId);
}
return ViewHttpResponse::redirect("/");
}
/**
* @param array<string> $request
* @param int $tacticId
* @param SessionHandle $session
* @return HttpResponse
*/
public function shareTacticToAccount(array $request, int $tacticId, SessionHandle $session): HttpResponse {
$email = $request["email"];
$this->tactics->shareTacticToAccountMail($email, $tacticId);
return ViewHttpResponse::redirect("/");
}
/**
* @param int $tacticId
* @param SessionHandle $session
* @return HttpResponse
*/
public function unshareTactic(int $tacticId, SessionHandle $session): HttpResponse {
$this->tactics->unshareTactic($tacticId);
$this->teams->unshareTactic($tacticId);
return ViewHttpResponse::redirect("/");
}
/**
* @param int $tacticId
* @param int $teamId
* @param SessionHandle $session
* @return HttpResponse
*/
public function unshareTacticToTeam(int $tacticId, int $teamId, SessionHandle $session): HttpResponse {
$this->teams->unshareTacticToTeam($tacticId, $teamId);
return ViewHttpResponse::redirect("/");
}
/**
* @param int $tacticId
* @param int $accountId
* @param SessionHandle $session
* @return HttpResponse
*/
public function unshareTacticToAccount(int $tacticId, int $accountId, SessionHandle $session): HttpResponse {
$this->tactics->unshareTacticToAccount($tacticId, $accountId);
return ViewHttpResponse::redirect("/");
}
}

@ -129,6 +129,7 @@ class TeamController {
], HttpCodes::FORBIDDEN);
}
$role = $this->model->isCoach($id, $session->getAccount()->getUser()->getEmail());
$tactics = $this->model->getAllTeamTactic($id);
return ViewHttpResponse::react(
'views/TeamPanel.tsx',
@ -138,7 +139,9 @@ class TeamController {
"members" => $result->listMembers(),
],
'isCoach' => $role,
'currentUserId' => $session->getAccount()->getUser()->getId()]
'currentUserId' => $session->getAccount()->getUser()->getId(),
'tactics' => $tactics,
]
);
}
@ -243,4 +246,10 @@ class TeamController {
$this->model->editTeam($idTeam, $request['name'], $request['picture'], $request['main_color'], $request['second_color']);
return HttpResponse::redirect('/team/' . $idTeam);
}
public function shareTactic(int $teamId, int $tacticId, SessionHandle $session): ViewHttpResponse {
$this->model->shareTacticToTeam($teamId, $tacticId);
return $this->displayTeam($teamId, $session);
}
}

@ -35,7 +35,15 @@ class UserController {
$lastTactics = $this->tactics->getLast($limitNbTactics, $user->getId());
$allTactics = $this->tactics->getAll($user->getId());
$name = $user->getName();
$allTacticsShared = $this->tactics->getAllTacticShared($user->getId());
if(isset($allTacticsShared)) {
foreach ($allTacticsShared as $tactic) {
if(!in_array($tactic, $allTactics)) {
array_push($allTactics, $tactic);
}
}
}
if ($this->teams != null) {
$teams = $this->teams->getAll($user->getId());
@ -47,7 +55,7 @@ class UserController {
"lastTactics" => $lastTactics,
"allTactics" => $allTactics,
"teams" => $teams,
"username" => $name,
"user" => $user,
]);
}
@ -62,5 +70,4 @@ class UserController {
$session->destroy();
return HttpResponse::redirect("/");
}
}

@ -15,5 +15,4 @@ class TacticValidator {
}
return null;
}
}

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Confirmation</title>
</head>
<body>
<div class="container">
<h2>Etes-vous sûr de vouloir partager la tactique ?</h2>
<form action="{{ path("/shareTactic/#{tactic}/team/#{team}") }}" method="POST">
<div>
<input type="radio" id="yes" name="confirmation" value="yes" />
<label for="yes">Oui</label>
</div>
<div>
<input type="radio" id="no" name="confirmation" value="no" />
<label for="no">Non</label>
</div>
<div class="form-group">
<input type="submit" value="Confirmer">
</div>
</form>
</div>
</body>
</html>

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tactiques partageables</title>
</head>
<body>
<header>
<h1><a href="{{ path('/') }}">IQBall</a></h1>
</header>
{% if toShare %}
{% for t in tactics %}
<div onclick="window.location.href = '{{ path("/shareTactic/#{t.id}") }}'">
<p> {{ t.name }} </p>
</div>
{% endfor %}
{% else %}
{% for t in tactics %}
<div onclick="window.location.href = '{{ path("/unshareTactic/#{t.id}") }}'">
<p> {{ t.name }} </p>
</div>
{% endfor %}
{% endif %}
</body>
</html>

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Twig view</title>
</head>
<body>
{% if toShare %}
{% if teams is empty %}
<p>Vous n'êtes dans aucune équipe</p>
{% else %}
{% for team in teams %}
<div class="team" onclick="window.location.href = '{{ path("/shareTactic/#{tactic}/team/#{team.id}") }}'">
<p>Nom de l'équipe : {{ team.name }}</p>
<img src="{{ team.picture }}" alt="logo de l'équipe">
</div>
{% endfor %}
{% endif %}
<h2>Partager à un compte</h2>
<form action="{{ path("/shareTactic/#{tactic}/account") }}" method="POST">
<div class="form-group">
<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 %}
<input type="text" id="email" name="email" required>
</div>
<div class="form-group">
<input type="submit" value="Confirmer">
</div>
</form>
{% else %}
<form action="{{ path("/unshareTactic/#{tactic}") }}" method="POST">
<button name="unshareAll" value="unshareAll">Supprimer le partage de cette tactique à tout le monde</button>
</form>
{% endif %}
</body>
</html>

@ -31,6 +31,7 @@
height: 100%;
width: 100%;
margin: 0;
overflow: visible;
}
</style>

@ -2,7 +2,7 @@
namespace IQBall\Core\Data;
class TacticInfo {
class TacticInfo implements \JsonSerializable {
private int $id;
private string $name;
private int $creationDate;
@ -59,4 +59,8 @@ class TacticInfo {
public function getCreationDate(): int {
return $this->creationDate;
}
public function jsonSerialize() {
return get_object_vars($this);
}
}

@ -17,6 +17,14 @@ class AccountGateway {
$this->con = $con;
}
/**
* @param string $name
* @param string $email
* @param string $token
* @param string $hash
* @param string $profilePicture
* @return int
*/
public function insertAccount(string $name, string $email, string $token, string $hash, string $profilePicture): int {
$this->con->exec("INSERT INTO Account(username, hash, email, token,profile_picture) VALUES (:username,:hash,:email,:token,:profile_pic)", [
':username' => [$name, PDO::PARAM_STR],
@ -107,6 +115,19 @@ class AccountGateway {
return new Account($acc["token"], new User($email, $acc["username"], $acc["id"], $acc["profile_picture"], $this->isAdmin($acc["id"])));
}
/**
* @param string $email
* @return array<array<string, mixed>>
*/
public function getAccountIdFromMail(string $email): array {
return $this->con->fetch(
"SELECT id FROM Account WHERE email = :mail",
[
":mail" => [$email, PDO::PARAM_STR],
]
);
}
/**
* @param string $token get an account from given token
* @return Account|null
@ -158,6 +179,52 @@ class AccountGateway {
}
/**
* @param int $accountId
* @param int $tacticId
* @return int
*/
public function shareTacticToAccount(int $accountId, int $tacticId): int {
$this->con->exec(
"INSERT INTO TacticSharedAccount(id_account, id_tactic) VALUES(:accountId, :tacticId)",
[
":accountId" => [$accountId, PDO::PARAM_INT],
":tacticId" => [$tacticId, PDO::PARAM_INT],
]
);
return intval($this->con->lastInsertId());
}
/**
* @param int $tacticId
* @return int
*/
public function unshareTactic(int $tacticId): int {
$this->con->exec(
"DELETE FROM TacticSharedAccount WHERE id_tactic = :tacticId",
[
":tacticId" => [$tacticId, PDO::PARAM_INT],
]
);
return intval($this->con->lastInsertId());
}
/**
* @param int $tacticId
* @param int $accountId
* @return int
*/
public function unshareTacticToAccount(int $tacticId, int $accountId): int {
$this->con->exec(
"DELETE FROM TacticSharedAccount WHERE id_tactic = :tacticId AND id_account = :accountId",
[
":tacticId" => [$tacticId, PDO::PARAM_INT],
":accountId" => [$accountId, PDO::PARAM_INT],
]
);
return intval($this->con->lastInsertId());
}
/**
* returns the total amount of accounts in the database
* @return int

@ -7,6 +7,8 @@ use IQBall\Core\Data\CourtType;
use IQBall\Core\Data\TacticInfo;
use PDO;
use function PHPStan\dumpType;
class TacticInfoGateway {
private Connection $con;
@ -43,9 +45,9 @@ class TacticInfoGateway {
* Return the nb last tactics created
*
* @param integer $nb
* @return array<array<string,mixed>>
* @return TacticInfo[] | null
*/
public function getLast(int $nb, int $ownerId): ?array {
public function getLastOwnedBy(int $nb, int $ownerId): ?array {
$res = $this->con->fetch(
"SELECT *
FROM Tactic
@ -59,15 +61,15 @@ class TacticInfoGateway {
if (count($res) == 0) {
return [];
}
return $res;
return array_map(fn(array $row) => $this->rowToTacticInfo($row), $res);
}
/**
* Get all the tactics of the owner
*
* @return array<array<string,mixed>>
* @return TacticInfo[] | null
*/
public function getAll(int $ownerId): ?array {
public function getAllOwnedBy(int $ownerId): ?array {
$res = $this->con->fetch(
"SELECT *
FROM Tactic
@ -80,7 +82,119 @@ class TacticInfoGateway {
if (count($res) == 0) {
return [];
}
return $res;
return array_map(fn(array $row) => $this->rowToTacticInfo($row), $res);
}
/**
* @param int $ownerId
* @return TacticInfo[]|null
*/
public function getAllTacticSharedTeam(int $ownerId): ?array {
$res = $this->con->fetch(
"SELECT t.* FROM Tactic t, TacticSharedTeam ts, Member m
WHERE ts.id_team = m.id_team AND ts.id_tactic = t.id
AND m.id_user = :ownerId",
[
":ownerId" => [$ownerId, PDO::PARAM_INT],
]
);
if (count($res) == 0) {
return [];
}
return array_map(fn(array $row) => $this->rowToTacticInfo($row), $res);
}
/**
* @param int $ownerId
* @return TacticInfo[]|null
*/
public function getAllTacticSharedAccount(int $ownerId): ?array {
$res = $this->con->fetch(
"SELECT t.* FROM Tactic t, TacticSharedAccount ta
WHERE t.id = ta.id_tactic AND ta.id_account = :ownerId",
[
":ownerId" => [$ownerId, PDO::PARAM_INT],
]
);
if (count($res) == 0) {
return [];
}
return array_map(fn(array $row) => $this->rowToTacticInfo($row), $res);
}
/**
* @param int $ownerId
* @return array<TacticInfo>|null
*/
public function getAllTacticSharedTeamOwned(int $ownerId): ?array {
$res = $this->con->fetch(
"SELECT t.* FROM Tactic t, TacticSharedTeam ts, Member m
WHERE ts.id_team = m.id_team AND ts.id_tactic = t.id
AND m.id_user = :ownerId AND t.owner = :ownerId",
[
":ownerId" => [$ownerId, PDO::PARAM_INT],
]
);
if (count($res) == 0) {
return [];
}
return array_map(fn(array $row) => $this->rowToTacticInfo($row), $res);
}
/**
* @param int $ownerId
* @return array<TacticInfo>|null
*/
public function getAllTacticSharedAccountOwned(int $ownerId): ?array {
$res = $this->con->fetch(
"SELECT t.* FROM Tactic t, TacticSharedAccount ta
WHERE t.id = ta.id_tactic AND t.owner = :ownerId",
[
":ownerId" => [$ownerId, PDO::PARAM_INT],
]
);
if (count($res) == 0) {
return [];
}
return array_map(fn(array $row) => $this->rowToTacticInfo($row), $res);
}
/**
* @param int $tacticId
* @param int $accountId
* @return array<array<string, mixed>>|null
*/
public function getOwnerIdTacticSharedTeam(int $tacticId, int $accountId): ?array {
return $this->con->fetch(
"SELECT t.owner
FROM Account a
JOIN Member m on a.id = m.id_user
JOIN TacticSharedTeam tt ON m.id_team = tt.id_team
JOIN Tactic t ON tt.id_tactic = t.id
WHERE tt.id_tactic = :tacticId AND m.id_user = :accountId",
[
":tacticId" => [$tacticId, PDO::PARAM_INT],
":accountId" => [$accountId, PDO::PARAM_INT],
]
);
}
/**
* @param int $tacticId
* @param int $accountId
* @return array<array<string, mixed>>|null
*/
public function getOwnerIdTacticSharedAccount(int $tacticId, int $accountId): ?array {
return $this->con->fetch(
"SELECT t.owner FROM Account a
JOIN TacticSharedAccount ta ON a.id = ta.id_account
JOIN Tactic t ON ta.id_tactic = t.id
WHERE ta.id_tactic = :tacticId AND ta.id_account = :accountId",
[
":tacticId" => [$tacticId, PDO::PARAM_INT],
":accountId" => [$accountId, PDO::PARAM_INT],
]
);
}
@ -148,4 +262,13 @@ class TacticInfoGateway {
]);
return $stmnt->rowCount() == 1;
}
/**
* @param array<mixed> $row
* @return TacticInfo
*/
private function rowToTacticInfo(array $row): TacticInfo {
$type = CourtType::fromName($row['court_type']);
return new TacticInfo($row['id'], $row["name"], strtotime($row["creation_date"]), $row["owner"], $type, $row['content']);
}
}

@ -3,8 +3,7 @@
namespace IQBall\Core\Gateway;
use IQBall\Core\Connection;
use IQBall\Core\Data\Color;
use IQBall\Core\Data\Team;
use IQBall\Core\Data\TacticInfo;
use IQBall\Core\Data\TeamInfo;
use PDO;
@ -35,6 +34,53 @@ class TeamGateway {
return intval($this->con->lastInsertId());
}
/**
* @param int $teamId
* @param int $tacticId
* @return int
*/
public function shareTacticToTeam(int $teamId, int $tacticId): int {
$this->con->exec(
"INSERT INTO TacticSharedTeam(id_team, id_tactic) VALUES(:teamId, :tacticId)",
[
":teamId" => [$teamId, PDO::PARAM_INT],
":tacticId" => [$tacticId, PDO::PARAM_INT],
]
);
return intval($this->con->lastInsertId());
}
/**
* @param int $tacticId
* @return int
*/
public function unshareTactic(int $tacticId): int {
$this->con->exec(
"DELETE FROM TacticSharedTeam WHERE id_tactic = :tacticId",
[
":tacticId" => [$tacticId, PDO::PARAM_INT],
]
);
return intval($this->con->lastInsertId());
}
/**
* @param int $tacticId
* @param int $teamId
* @return int
*/
public function unshareTacticToTeam(int $tacticId, int $teamId): int {
$this->con->exec(
"DELETE FROM TacticSharedTeam WHERE id_tactic = :tacticId AND id_team = :teamId",
[
":tacticId" => [$tacticId, PDO::PARAM_INT],
":teamId" => [$teamId, PDO::PARAM_INT],
]
);
return intval($this->con->lastInsertId());
}
/**
* @param string $name
* @param int $id
@ -140,6 +186,32 @@ class TeamGateway {
);
}
/**
* @param int $user
* @return array<array<string, mixed>>
*/
public function getAllIsCoach(int $user): array {
return $this->con->fetch(
"SELECT t.* FROM team t,Member m WHERE m.id_team = t.id AND m.id_user= :idUser AND m.role = 'COACH'",
[
"idUser" => [$user, PDO::PARAM_INT],
]
);
}
/**
* @param int $team
* @return array<array<string, mixed>>
*/
public function getAllTeamTactic(int $team): array {
return $this->con->fetch(
"SELECT t.* FROM Tactic t, TacticSharedTeam tt WHERE t.id = tt.id_tactic AND tt.id_team = :idTeam",
[
"idTeam" => [$team, PDO::PARAM_INT],
]
);
}
/**
* @param int $start
* @param int $n
@ -182,5 +254,4 @@ class TeamGateway {
}
}
}

@ -2,9 +2,11 @@
namespace IQBall\Core\Model;
use IQBall\Core\Data\Account;
use IQBall\Core\Data\CourtType;
use IQBall\App\Session\SessionHandle;
use IQBall\Core\Data\TacticInfo;
use IQBall\Core\Gateway\AccountGateway;
use IQBall\Core\Gateway\TacticInfoGateway;
use IQBall\Core\Validation\ValidationFail;
@ -12,12 +14,14 @@ class TacticModel {
public const TACTIC_DEFAULT_NAME = "Nouvelle tactique";
private TacticInfoGateway $tactics;
private AccountGateway $users;
/**
* @param TacticInfoGateway $tactics
*/
public function __construct(TacticInfoGateway $tactics) {
public function __construct(TacticInfoGateway $tactics, AccountGateway $users) {
$this->tactics = $tactics;
$this->users = $users;
}
/**
@ -56,10 +60,10 @@ class TacticModel {
*
* @param integer $nb
* @param integer $ownerId
* @return array<array<string,mixed>>
* @return array<TacticInfo>
*/
public function getLast(int $nb, int $ownerId): array {
return $this->tactics->getLast($nb, $ownerId);
return $this->tactics->getLastOwnedBy($nb, $ownerId);
}
@ -78,11 +82,159 @@ class TacticModel {
* Get all the tactics of the owner
*
* @param integer $ownerId
* @return array<array<string,mixed>>
* @return array<TacticInfo>|null
*/
public function getAll(int $ownerId): ?array {
return $this->tactics->getAll($ownerId);
return $this->tactics->getAllOwnedBy($ownerId);
}
/**
* Get all the tactics shared to a user via team share
* @param int $ownerId
* @return array<TacticInfo>|null
*/
public function getAllTacticSharedTeam(int $ownerId): ?array {
return $this->tactics->getAllTacticSharedTeam($ownerId);
}
/**
* Get all the tactics shared to a user via account share
* @param int $ownerId
* @return array<TacticInfo>|null
*/
public function getAllTacticSharedAccount(int $ownerId): ?array {
return $this->tactics->getAllTacticSharedAccount($ownerId);
}
/**
* Get all the tactics shared to a user via team or account share
* @param int $ownerId
* @return array<TacticInfo>|null
*/
public function getAllTacticShared(int $ownerId): ?array {
$allTactics = [];
$allTacticsSharedTeam = $this->tactics->getAllTacticSharedTeam($ownerId);
if(isset($allTacticsSharedTeam)) {
foreach ($allTacticsSharedTeam as $tactic) {
if(!in_array($tactic, $allTactics)) {
array_push($allTactics, $tactic);
}
}
}
$allTacticsSharedAccount = $this->tactics->getAllTacticSharedAccount($ownerId);
if(isset($allTacticsSharedAccount)) {
foreach ($allTacticsSharedAccount as $tactic) {
if(!in_array($tactic, $allTactics)) {
array_push($allTactics, $tactic);
}
}
}
return $allTactics;
}
/**
* Get all the tactics a user shared and own
* @param int $ownerId
* @return array<TacticInfo>|null
*/
public function getAllTacticSharedOwned(int $ownerId): ?array {
$allTactics = [];
$allTacticsSharedTeamOwned = $this->tactics->getAllTacticSharedTeamOwned($ownerId);
if(isset($allTacticsSharedTeamOwned)) {
foreach ($allTacticsSharedTeamOwned as $tactic) {
if(!in_array($tactic, $allTactics)) {
array_push($allTactics, $tactic);
}
}
}
$allTacticsSharedAccountOwned = $this->tactics->getAllTacticSharedAccountOwned($ownerId);
if(isset($allTacticsSharedAccountOwned)) {
foreach ($allTacticsSharedAccountOwned as $tactic) {
if(!in_array($tactic, $allTactics)) {
array_push($allTactics, $tactic);
}
}
}
return $allTactics;
}
/**
* Share tactic to an account
* @param int $accountId
* @param int $tacticId
* @return int
*/
public function shareTacticToAccount(int $accountId, int $tacticId): int {
return $this->users->shareTacticToAccount($accountId, $tacticId);
}
/**
* Share tactic to an account email
* @param string $email
* @param int $tacticId
* @return int|null
*/
public function shareTacticToAccountMail(string $email, int $tacticId): ?int {
$account = $this->users->getAccountFromMail($email);
$accountId = $account->getUser()->getId();
return $this->shareTacticToAccount($accountId, $tacticId);
}
/**
* Unshare a tactic to every account
* @param int $tacticId
* @return int
*/
public function unshareTactic(int $tacticId): int {
return $this->users->unshareTactic($tacticId);
}
/**
* Unshare a tactic to an account
* @param int $tacticId
* @param int $accountId
* @return int
*/
public function unshareTacticToAccount(int $tacticId, int $accountId): int {
return $this->users->unshareTacticToAccount($tacticId, $accountId);
}
/**
* Get the owner of a shared tactic
* @param int $tactidId
* @param int $accountId
* @return array<int>|null
*/
public function getOwnerIdTacticShared(int $tactidId, int $accountId): ?array {
$ids = [];
$idT = $this->tactics->getOwnerIdTacticSharedTeam($tactidId, $accountId);
if(isset($idT)) {
foreach ($idT as $id) {
if(!in_array($id, $ids)) {
array_push($ids, $id);
}
}
}
$idA = $this->tactics->getOwnerIdTacticSharedAccount($tactidId, $accountId);
if(isset($idA)) {
foreach ($idA as $id) {
if(!in_array($id, $ids)) {
array_push($ids, $id);
}
}
}
if($ids != null) {
return $ids[0];
}
return null;
}
/**
* Update the name of a tactic
* @param int $id the tactic identifier
@ -107,6 +259,7 @@ class TacticModel {
return [];
}
public function updateContent(int $id, string $json): ?ValidationFail {
if (!$this->tactics->updateContent($id, $json)) {
return ValidationFail::error("Could not update content");
@ -114,4 +267,16 @@ class TacticModel {
return null;
}
/**
* Verify if tactic can be shared by user
* @param int $id
* @param Account $account
* @return bool
*/
public function canShareTactic(int $id, Account $account): bool {
if($this->get($id)->getOwnerId() != $account->getUser()->getId()) {
return false;
}
return true;
}
}

@ -78,6 +78,48 @@ class TeamModel {
return new Team($teamInfo, $members);
}
/**
* Share tactic to a team
* @param int $teamId
* @param int $tacticId
* @return int
*/
public function shareTacticToTeam(int $teamId, int $tacticId): int {
return $this->teams->shareTacticToTeam($teamId, $tacticId);
}
/**
* Unshare tactic from every team
* @param int $tacticId
* @return int
*/
public function unshareTactic(int $tacticId): int {
return $this->teams->unshareTactic($tacticId);
}
/**
* Unshare team-shared tactic
* @param int $tacticId
* @param int $teamId
* @return int
*/
public function unshareTacticToTeam(int $tacticId, int $teamId): int {
return $this->teams->unshareTacticToTeam($tacticId, $teamId);
}
/**
* Verify if user can share tactic to a team
* @param int $teamId
* @param string $email
* @return bool
*/
public function canShareTacticToTeam(int $teamId, string $email): bool {
if($this->isCoach($teamId, $email)) {
return true;
}
return false;
}
/**
* delete a member from given team identifier
* @param int $idMember
@ -140,6 +182,24 @@ class TeamModel {
return $this->teams->getAll($user);
}
/**
* Get all user's teams where user is coach
* @param int $user
* @return array<array<string, mixed>>
*/
public function getAllIsCoach(int $user): array {
return $this->teams->getAllIsCoach($user);
}
/**
* Get all tactic shared with a team
* @param int $team
* @return array<array<string, mixed>>
*/
public function getAllTeamTactic(int $team): array {
return $this->teams->getAllTeamTactic($team);
}
/**
* @param int $start
* @param int $n

Loading…
Cancel
Save