@ -0,0 +1,18 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
@ -1,46 +1,26 @@
|
|||||||
.vs
|
# Logs
|
||||||
.vscode
|
logs
|
||||||
.idea
|
*.log
|
||||||
.code
|
npm-debug.log*
|
||||||
.vite
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
vendor
|
node_modules
|
||||||
.nfs*
|
|
||||||
composer.lock
|
|
||||||
*.phar
|
|
||||||
dist
|
dist
|
||||||
.guard
|
dist-ssr
|
||||||
outputs
|
*.local
|
||||||
|
|
||||||
# sqlite database files
|
|
||||||
*.sqlite
|
|
||||||
|
|
||||||
views-mappings.php
|
|
||||||
.env.PROD
|
|
||||||
|
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
package-lock.json
|
|
||||||
|
|
||||||
# testing
|
# Editor directories and files
|
||||||
/coverage
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
# production
|
.idea
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
*.suo
|
||||||
.env.development.local
|
*.ntvs*
|
||||||
.env.test.local
|
*.njsproj
|
||||||
.env.production.local
|
*.sln
|
||||||
|
*.sw?
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
.php-cs-fixer.cache
|
package-lock.json
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"IQBall\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"altorouter/altorouter": "1.2.0",
|
|
||||||
"ext-json": "*",
|
|
||||||
"ext-pdo": "*",
|
|
||||||
"ext-pdo_sqlite": "*",
|
|
||||||
"twig/twig":"^2.0",
|
|
||||||
"phpstan/phpstan": "*"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"friendsofphp/php-cs-fixer": "^3.38"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// `dev-config-profile.php` by default.
|
|
||||||
// on production server the included profile is `prod-config-profile.php`.
|
|
||||||
// Please do not touch.
|
|
||||||
|
|
||||||
require /*PROFILE_FILE*/ "profiles/dev-config-profile.php";
|
|
||||||
|
|
||||||
const SUPPORTS_FAST_REFRESH = _SUPPORTS_FAST_REFRESH;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps the given relative source uri (relative to the `/front` folder) to its actual location depending on imported profile.
|
|
||||||
* @param string $assetURI relative uri path from `/front` folder
|
|
||||||
* @return string valid url that points to the given uri
|
|
||||||
|
|
||||||
*/
|
|
||||||
function asset(string $assetURI): string {
|
|
||||||
return _asset($assetURI);
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_base_path(): string {
|
|
||||||
return _get_base_path();
|
|
||||||
}
|
|
||||||
|
|
||||||
global $_data_source_name;
|
|
||||||
$data_source_name = $_data_source_name;
|
|
||||||
const DATABASE_USER = _DATABASE_USER;
|
|
||||||
const DATABASE_PASSWORD = _DATABASE_PASSWORD;
|
|
||||||
|
|
||||||
|
|
||||||
function init_database(PDO $pdo): void {
|
|
||||||
_init_database($pdo);
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
## verify php and typescript types
|
|
||||||
|
|
||||||
echo "formatting php typechecking"
|
|
||||||
vendor/bin/php-cs-fixer fix
|
|
||||||
|
|
||||||
echo "formatting typescript typechecking"
|
|
||||||
npm run format
|
|
@ -1,19 +0,0 @@
|
|||||||
import ReactDOM from "react-dom/client"
|
|
||||||
import React, { FunctionComponent } from "react"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamically renders a React component, with given arguments
|
|
||||||
* @param Component the react component to render
|
|
||||||
* @param args the arguments to pass to the react component.
|
|
||||||
*/
|
|
||||||
export function renderView(Component: FunctionComponent, args: {}) {
|
|
||||||
const root = ReactDOM.createRoot(
|
|
||||||
document.getElementById("root") as HTMLElement,
|
|
||||||
)
|
|
||||||
|
|
||||||
root.render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<Component {...args} />
|
|
||||||
</React.StrictMode>,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
:root {
|
|
||||||
--main-color: #191a21;
|
|
||||||
--second-color: #282a36;
|
|
||||||
--third-color: #303341;
|
|
||||||
--accent-color: #ffa238;
|
|
||||||
--main-contrast-color: #e6edf3;
|
|
||||||
--font-title: Helvetica;
|
|
||||||
--font-content: Helvetica;
|
|
||||||
}
|
|
@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/src/assets/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + React + TS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,12 +0,0 @@
|
|||||||
parameters:
|
|
||||||
phpVersion: 70400
|
|
||||||
level: 6
|
|
||||||
paths:
|
|
||||||
- src
|
|
||||||
scanFiles:
|
|
||||||
- config.php
|
|
||||||
- sql/database.php
|
|
||||||
- profiles/dev-config-profile.php
|
|
||||||
- profiles/prod-config-profile.php
|
|
||||||
excludePaths:
|
|
||||||
- src/App/react-display-file.php
|
|
@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use IQBall\Core\Connection;
|
|
||||||
use IQBall\Core\Gateway\AccountGateway;
|
|
||||||
use IQBall\Core\Model\AuthModel;
|
|
||||||
|
|
||||||
$hostname = getHostName();
|
|
||||||
$front_url = "http://$hostname:5173";
|
|
||||||
|
|
||||||
const _SUPPORTS_FAST_REFRESH = true;
|
|
||||||
$_data_source_name = "sqlite:${_SERVER['DOCUMENT_ROOT']}/../dev-database.sqlite";
|
|
||||||
|
|
||||||
// no user and password needed for sqlite databases
|
|
||||||
const _DATABASE_USER = null;
|
|
||||||
const _DATABASE_PASSWORD = null;
|
|
||||||
|
|
||||||
function _asset(string $assetURI): string {
|
|
||||||
global $front_url;
|
|
||||||
return $front_url . "/" . $assetURI;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _init_database(PDO $pdo): void {
|
|
||||||
$accounts = new AccountGateway(new Connection($pdo));
|
|
||||||
$teams = new \IQBall\Core\Gateway\TeamGateway((new Connection($pdo)));
|
|
||||||
|
|
||||||
$defaultAccounts = ["maxime", "mael", "yanis", "vivien"];
|
|
||||||
$defaultTeams = ["Lakers", "Celtics", "Bulls"];
|
|
||||||
|
|
||||||
|
|
||||||
foreach ($defaultAccounts as $name) {
|
|
||||||
$email = "$name@mail.com";
|
|
||||||
$id = $accounts->insertAccount($name, $email, AuthModel::generateToken(), password_hash("123456", PASSWORD_DEFAULT), "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png");
|
|
||||||
$accounts->setIsAdmin($id, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($defaultTeams as $name) {
|
|
||||||
$id = $teams->insert($name, "https://lebasketographe.fr/wp-content/uploads/2019/11/nom-equipes-nba.jpg", "#1a2b3c", "#FF00AA");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _get_base_path(): string {
|
|
||||||
return "";
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// This file only exists on production servers, and defines the available assets mappings
|
|
||||||
// in an `ASSETS` array constant.
|
|
||||||
require __DIR__ . "/../views-mappings.php";
|
|
||||||
|
|
||||||
// THIS VALUE IS TO SET IN THE CI
|
|
||||||
const BASE_PATH = null;
|
|
||||||
|
|
||||||
const _SUPPORTS_FAST_REFRESH = false;
|
|
||||||
$database_file = __DIR__ . "/../database.sqlite";
|
|
||||||
$_data_source_name = "sqlite:/$database_file";
|
|
||||||
|
|
||||||
// no user and password needed for sqlite databases
|
|
||||||
const _DATABASE_USER = null;
|
|
||||||
const _DATABASE_PASSWORD = null;
|
|
||||||
|
|
||||||
|
|
||||||
function _asset(string $assetURI): string {
|
|
||||||
// use index.php's base path
|
|
||||||
global $basePath;
|
|
||||||
// If the asset uri does not figure in the available assets array,
|
|
||||||
// fallback to the uri itself.
|
|
||||||
return $basePath . "/" . (ASSETS[$assetURI] ?? $assetURI);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function _init_database(PDO $pdo): void {}
|
|
||||||
|
|
||||||
function _get_base_path(): string {
|
|
||||||
return BASE_PATH;
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
RewriteEngine on
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
RewriteRule ^.*$ ./index.php [NC,L,QSA]
|
|
@ -1,4 +0,0 @@
|
|||||||
RewriteEngine on
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
|
||||||
RewriteCond %{REQUEST_FILENAME} !-d
|
|
||||||
RewriteRule ^.*$ ./index.php [NC,L,QSA]
|
|
@ -1,99 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require "../../config.php";
|
|
||||||
require "../../vendor/autoload.php";
|
|
||||||
require "../../sql/database.php";
|
|
||||||
|
|
||||||
use IQBall\Api\API;
|
|
||||||
use IQBall\Api\Controller\APIAccountsController;
|
|
||||||
use IQBall\Api\Controller\APIAuthController;
|
|
||||||
use IQBall\Api\Controller\APIServerController;
|
|
||||||
use IQBall\Api\Controller\APITacticController;
|
|
||||||
use IQBall\Api\Controller\APITeamController;
|
|
||||||
use IQBall\App\Session\PhpSessionHandle;
|
|
||||||
use IQBall\Core\Action;
|
|
||||||
use IQBall\Core\Connection;
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
use IQBall\Core\Gateway\AccountGateway;
|
|
||||||
use IQBall\Core\Gateway\MemberGateway;
|
|
||||||
use IQBall\Core\Gateway\TacticInfoGateway;
|
|
||||||
use IQBall\Core\Gateway\TeamGateway;
|
|
||||||
use IQBall\Core\Model\AuthModel;
|
|
||||||
use IQBall\Core\Model\TacticModel;
|
|
||||||
use IQBall\Core\Model\TeamModel;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
|
|
||||||
$basePath = get_base_path() . "/api";
|
|
||||||
|
|
||||||
function getTacticController(): APITacticController {
|
|
||||||
return new APITacticController(new TacticModel(new TacticInfoGateway(new Connection(get_database()))));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAuthController(): APIAuthController {
|
|
||||||
return new APIAuthController(new AuthModel(new AccountGateway(new Connection(get_database()))));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAccountController(): APIAccountsController {
|
|
||||||
$con = new Connection(get_database());
|
|
||||||
$gw = new AccountGateway($con);
|
|
||||||
return new APIAccountsController(new AuthModel($gw), $gw);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function getServerController(): APIServerController {
|
|
||||||
global $basePath;
|
|
||||||
return new APIServerController($basePath, get_database());
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAPITeamController(): APITeamController {
|
|
||||||
$con = new Connection(get_database());
|
|
||||||
return new APITeamController(new TeamModel(new TeamGateway($con), new MemberGateway($con), new AccountGateway($con)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRoutes(): AltoRouter {
|
|
||||||
global $basePath;
|
|
||||||
$router = new AltoRouter();
|
|
||||||
$router->setBasePath($basePath);
|
|
||||||
|
|
||||||
$router->map("OPTIONS", "*", Action::noAuth(fn() => HttpResponse::fromCode(HttpCodes::OK)));
|
|
||||||
|
|
||||||
$router->map("POST", "/auth", Action::noAuth(fn() => getAuthController()->authorize()));
|
|
||||||
$router->map("POST", "/tactic/[i:id]/edit/name", Action::auth(fn(int $id, Account $acc) => getTacticController()->updateName($id, $acc)));
|
|
||||||
$router->map("POST", "/tactic/[i:id]/save", Action::auth(fn(int $id, Account $acc) => getTacticController()->saveContent($id, $acc)));
|
|
||||||
$router->map("GET", "/admin/list-users", Action::noAuth(fn() => getAccountController()->listUsers($_GET)));
|
|
||||||
$router->map("GET", "/admin/user/[i:id]", Action::noAuth(fn(int $id) => getAccountController()->getUser($id)));
|
|
||||||
$router->map("GET", "/admin/user/[i:id]/space", Action::noAuth(fn(int $id) => getTacticController()->getUserTactics($id)));
|
|
||||||
$router->map("POST", "/admin/user/add", Action::noAuth(fn() => getAccountController()->addUser()));
|
|
||||||
$router->map("POST", "/admin/user/remove-all", Action::noAuth(fn() => getAccountController()->removeUsers()));
|
|
||||||
$router->map("POST", "/admin/user/[i:id]/update", Action::noAuth(fn(int $id) => getAccountController()->updateUser($id)));
|
|
||||||
$router->map("GET", "/admin/server-info", Action::noAuth(fn() => getServerController()->getServerInfo()));
|
|
||||||
$router->map("GET", "/admin/list-team", Action::noAuth(fn() => getAPITeamController()->listTeams($_GET)));
|
|
||||||
$router->map("POST", "/admin/add-team", Action::noAuth(fn() => getAPITeamController()->addTeam()));
|
|
||||||
$router->map("POST", "/admin/delete-teams", Action::noAuth(fn() => getAPITeamController()->deleteTeamSelected()));
|
|
||||||
$router->map("POST", "/admin/team/[i:id]/update", Action::noAuth(fn(int $id) => getAPITeamController()->updateTeam($id)));
|
|
||||||
|
|
||||||
return $router;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the way of being authorised through the API
|
|
||||||
* By checking if an Authorisation header is set, and by expecting its value to be a valid token of an account.
|
|
||||||
* If the header is not set, fallback to the App's PHP session system, and try to extract the account from it.
|
|
||||||
* @return Account|null
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
function tryGetAuthorization(): ?Account {
|
|
||||||
$headers = getallheaders();
|
|
||||||
|
|
||||||
// If no authorization header is set, try fallback to php session.
|
|
||||||
if (!isset($headers['Authorization'])) {
|
|
||||||
$session = PhpSessionHandle::init();
|
|
||||||
return $session->getAccount();
|
|
||||||
}
|
|
||||||
$token = $headers['Authorization'];
|
|
||||||
$gateway = new AccountGateway(new Connection(get_database()));
|
|
||||||
return $gateway->getAccountFromToken($token);
|
|
||||||
}
|
|
||||||
|
|
||||||
Api::consume(API::handleMatch(getRoutes()->match(), fn() => tryGetAuthorization()));
|
|
@ -1 +0,0 @@
|
|||||||
../front/assets
|
|
@ -1 +0,0 @@
|
|||||||
../front
|
|
@ -1,129 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require "../vendor/autoload.php";
|
|
||||||
require "../config.php";
|
|
||||||
require "../sql/database.php";
|
|
||||||
require "../src/App/react-display.php";
|
|
||||||
|
|
||||||
use IQBall\App\App;
|
|
||||||
use IQBall\App\Controller\AuthController;
|
|
||||||
use IQBall\App\Controller\EditorController;
|
|
||||||
use IQBall\App\Controller\TeamController;
|
|
||||||
use IQBall\App\Controller\UserController;
|
|
||||||
use IQBall\App\Controller\VisualizerController;
|
|
||||||
use IQBall\App\Session\MutableSessionHandle;
|
|
||||||
use IQBall\App\Session\PhpSessionHandle;
|
|
||||||
use IQBall\App\Session\SessionHandle;
|
|
||||||
use IQBall\App\ViewHttpResponse;
|
|
||||||
use IQBall\Core\Action;
|
|
||||||
use IQBall\Core\Connection;
|
|
||||||
use IQBall\Core\Data\CourtType;
|
|
||||||
use IQBall\Core\Gateway\AccountGateway;
|
|
||||||
use IQBall\Core\Gateway\MemberGateway;
|
|
||||||
use IQBall\Core\Gateway\TacticInfoGateway;
|
|
||||||
use IQBall\Core\Gateway\TeamGateway;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Model\AuthModel;
|
|
||||||
use IQBall\Core\Model\TacticModel;
|
|
||||||
use IQBall\Core\Model\TeamModel;
|
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
|
||||||
use Twig\Environment;
|
|
||||||
use Twig\Loader\FilesystemLoader;
|
|
||||||
use Twig\TwigFunction;
|
|
||||||
|
|
||||||
function getConnection(): Connection {
|
|
||||||
return new Connection(get_database());
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUserController(): UserController {
|
|
||||||
return new UserController(new TacticModel(new TacticInfoGateway(getConnection())), new TeamModel(new TeamGateway(getConnection()), new MemberGateway(getConnection()), new AccountGateway(getConnection())));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVisualizerController(): VisualizerController {
|
|
||||||
return new VisualizerController(new TacticModel(new TacticInfoGateway(getConnection())));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEditorController(): EditorController {
|
|
||||||
return new EditorController(new TacticModel(new TacticInfoGateway(getConnection())));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTeamController(): TeamController {
|
|
||||||
$con = getConnection();
|
|
||||||
return new TeamController(new TeamModel(new TeamGateway($con), new MemberGateway($con), new AccountGateway($con)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAuthController(): AuthController {
|
|
||||||
return new AuthController(new AuthModel(new AccountGateway(getConnection())));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTwig(): Environment {
|
|
||||||
global $basePath;
|
|
||||||
$fl = new FilesystemLoader("../src/App/Views");
|
|
||||||
$twig = new Environment($fl);
|
|
||||||
|
|
||||||
$twig->addFunction(new TwigFunction('path', fn(string $str) => "$basePath$str"));
|
|
||||||
|
|
||||||
return $twig;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRoutes(): AltoRouter {
|
|
||||||
global $basePath;
|
|
||||||
|
|
||||||
$ar = new AltoRouter();
|
|
||||||
$ar->setBasePath($basePath);
|
|
||||||
|
|
||||||
//authentication
|
|
||||||
$ar->map("GET", "/login", Action::noAuth(fn() => getAuthController()->displayLogin()));
|
|
||||||
$ar->map("GET", "/register", Action::noAuth(fn() => getAuthController()->displayRegister()));
|
|
||||||
$ar->map("POST", "/login", Action::noAuth(fn(SessionHandle $s) => getAuthController()->login($_POST, $s)));
|
|
||||||
$ar->map("POST", "/register", Action::noAuth(fn(SessionHandle $s) => getAuthController()->register($_POST, $s)));
|
|
||||||
|
|
||||||
//user-related
|
|
||||||
$ar->map("GET", "/", Action::auth(fn(SessionHandle $s) => getUserController()->home($s)));
|
|
||||||
$ar->map("GET", "/home", Action::auth(fn(SessionHandle $s) => getUserController()->home($s)));
|
|
||||||
$ar->map("GET", "/settings", Action::auth(fn(SessionHandle $s) => getUserController()->settings($s)));
|
|
||||||
$ar->map("GET", "/disconnect", Action::auth(fn(MutableSessionHandle $s) => getUserController()->disconnect($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)));
|
|
||||||
// don't require an authentication to run this action.
|
|
||||||
// If the user is not connected, the tactic will never save.
|
|
||||||
$ar->map("GET", "/tactic/new", Action::noAuth(fn() => getEditorController()->createNew()));
|
|
||||||
$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)));
|
|
||||||
$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/[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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function runMatch($match, MutableSessionHandle $session): HttpResponse {
|
|
||||||
global $basePath;
|
|
||||||
if (!$match) {
|
|
||||||
return ViewHttpResponse::twig("error.html.twig", [
|
|
||||||
'failures' => [ValidationFail::notFound("Could not find page {$_SERVER['REQUEST_URI']}.")],
|
|
||||||
], HttpCodes::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
return App::runAction($basePath . '/login', $match['target'], $match['params'], $session);
|
|
||||||
}
|
|
||||||
|
|
||||||
//this is a global variable
|
|
||||||
$basePath = get_base_path();
|
|
||||||
|
|
||||||
App::render(runMatch(getRoutes()->match(), PhpSessionHandle::init()), fn() => getTwig());
|
|
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use IQBall\Core\Connection;
|
|
||||||
use IQBall\Core\Gateway\AccountGateway;
|
|
||||||
use IQBall\Core\Model\AuthModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return PDO The PDO instance of the configuration's database connection.
|
|
||||||
*/
|
|
||||||
function get_database(): PDO {
|
|
||||||
// defined by profiles.
|
|
||||||
global $data_source_name;
|
|
||||||
$pdo = new PDO($data_source_name, DATABASE_USER, DATABASE_PASSWORD, [PDO::ERRMODE_EXCEPTION]);
|
|
||||||
|
|
||||||
$database_exists = $pdo->query("SELECT COUNT(*) FROM sqlite_master WHERE type = 'table'")->fetchColumn() > 0;
|
|
||||||
|
|
||||||
if ($database_exists) {
|
|
||||||
return $pdo;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (scandir(__DIR__) as $file) {
|
|
||||||
if (preg_match("/.*\.sql$/i", $file)) {
|
|
||||||
$content = file_get_contents(__DIR__ . "/" . $file);
|
|
||||||
|
|
||||||
$pdo->exec($content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init_database($pdo);
|
|
||||||
|
|
||||||
return $pdo;
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
-- drop tables here
|
|
||||||
DROP TABLE IF EXISTS Account;
|
|
||||||
DROP TABLE IF EXISTS Tactic;
|
|
||||||
DROP TABLE IF EXISTS Team;
|
|
||||||
DROP TABLE IF EXISTS User;
|
|
||||||
DROP TABLE IF EXISTS Member;
|
|
||||||
|
|
||||||
CREATE TABLE Admins
|
|
||||||
(
|
|
||||||
id integer PRIMARY KEY REFERENCES Account
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE Account
|
|
||||||
(
|
|
||||||
id integer PRIMARY KEY AUTOINCREMENT,
|
|
||||||
email varchar UNIQUE NOT NULL,
|
|
||||||
username varchar NOT NULL,
|
|
||||||
token varchar UNIQUE NOT NULL,
|
|
||||||
hash varchar NOT NULL,
|
|
||||||
profile_picture varchar NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE Tactic
|
|
||||||
(
|
|
||||||
id integer PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name varchar NOT NULL,
|
|
||||||
creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
||||||
owner integer NOT NULL,
|
|
||||||
content varchar DEFAULT '{"components": []}' NOT NULL,
|
|
||||||
court_type varchar CHECK ( court_type IN ('HALF', 'PLAIN')) NOT NULL,
|
|
||||||
FOREIGN KEY (owner) REFERENCES Account
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE Team
|
|
||||||
(
|
|
||||||
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
||||||
name varchar NOT NULL,
|
|
||||||
picture varchar NOT NULL,
|
|
||||||
main_color varchar NOT NULL,
|
|
||||||
second_color varchar NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE Member
|
|
||||||
(
|
|
||||||
id_team integer NOT NULL,
|
|
||||||
id_user integer NOT NULL,
|
|
||||||
role text CHECK (role IN ('COACH', 'PLAYER')) NOT NULL,
|
|
||||||
FOREIGN KEY (id_team) REFERENCES Team (id),
|
|
||||||
FOREIGN KEY (id_user) REFERENCES Account (id)
|
|
||||||
);
|
|
@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Api;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use IQBall\Core\Action;
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
|
||||||
|
|
||||||
class API {
|
|
||||||
public static function consume(HttpResponse $response): void {
|
|
||||||
http_response_code($response->getCode());
|
|
||||||
|
|
||||||
header('Access-Control-Allow-Origin: *');
|
|
||||||
header('Access-Control-Allow-Headers: *');
|
|
||||||
|
|
||||||
|
|
||||||
foreach ($response->getHeaders() as $header => $value) {
|
|
||||||
header("$header: $value");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($response instanceof JsonHttpResponse) {
|
|
||||||
header('Content-type: application/json');
|
|
||||||
echo $response->getJson();
|
|
||||||
} elseif (get_class($response) != HttpResponse::class) {
|
|
||||||
throw new Exception("API returned unknown Http Response");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed>|false $match
|
|
||||||
* @param callable(): Account $tryGetAuthorization function to return account authorisation for the given action (if required)
|
|
||||||
* @return HttpResponse
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static function handleMatch($match, callable $tryGetAuthorization): HttpResponse {
|
|
||||||
if (!$match) {
|
|
||||||
return new JsonHttpResponse([ValidationFail::notFound("not found")], HttpCodes::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
$action = $match['target'];
|
|
||||||
if (!$action instanceof Action) {
|
|
||||||
throw new Exception("routed action is not an AppAction object.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$account = null;
|
|
||||||
|
|
||||||
if ($action->getAuthType() != Action::NO_AUTH) {
|
|
||||||
$account = call_user_func($tryGetAuthorization);
|
|
||||||
if ($account == null) {
|
|
||||||
return new JsonHttpResponse([ValidationFail::unauthorized("Missing or invalid 'Authorization' header.")], HttpCodes::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($action->getAuthType() == Action::AUTH_ADMIN && !$account->getUser()->isAdmin()) {
|
|
||||||
return new JsonHttpResponse([ValidationFail::unauthorized()], HttpCodes::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $action->run($match['params'], $account);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Api;
|
|
||||||
|
|
||||||
use IQBall\Core\Control;
|
|
||||||
use IQBall\Core\ControlSchemaErrorResponseFactory;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpRequest;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|
||||||
use IQBall\Core\Validation\Validator;
|
|
||||||
|
|
||||||
class APIControl {
|
|
||||||
private static function errorFactory(): ControlSchemaErrorResponseFactory {
|
|
||||||
return new class () implements ControlSchemaErrorResponseFactory {
|
|
||||||
public function apply(array $failures): HttpResponse {
|
|
||||||
return new JsonHttpResponse($failures, HttpCodes::BAD_REQUEST);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs given callback, if the request's payload json validates the given schema.
|
|
||||||
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
|
||||||
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
|
||||||
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public static function runChecked(array $schema, callable $run): HttpResponse {
|
|
||||||
return Control::runChecked($schema, $run, self::errorFactory());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs given callback, if the given request data array validates the given schema.
|
|
||||||
* @param array<string, mixed> $data the request's data array.
|
|
||||||
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
|
||||||
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
|
||||||
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public static function runCheckedFrom(array $data, array $schema, callable $run): HttpResponse {
|
|
||||||
return Control::runCheckedFrom($data, $schema, $run, self::errorFactory());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Api\Controller;
|
|
||||||
|
|
||||||
use IQBall\Api\APIControl;
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
use IQBall\Core\Gateway\AccountGateway;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpRequest;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|
||||||
use IQBall\Core\Model\AuthModel;
|
|
||||||
use IQBall\Core\Validation\DefaultValidators;
|
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
|
||||||
|
|
||||||
class APIAccountsController {
|
|
||||||
private AccountGateway $accounts;
|
|
||||||
private AuthModel $authModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param AuthModel $model
|
|
||||||
* @param AccountGateway $accounts
|
|
||||||
*/
|
|
||||||
public function __construct(AuthModel $model, AccountGateway $accounts) {
|
|
||||||
$this->accounts = $accounts;
|
|
||||||
$this->authModel = $model;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $request
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function listUsers(array $request): HttpResponse {
|
|
||||||
return APIControl::runCheckedFrom($request, [
|
|
||||||
'start' => [DefaultValidators::isUnsignedInteger()],
|
|
||||||
'n' => [DefaultValidators::isIntInRange(0, 250)],
|
|
||||||
'search' => [DefaultValidators::lenBetween(0, 256)],
|
|
||||||
], function (HttpRequest $req) {
|
|
||||||
$accounts = $this->accounts->searchAccounts(intval($req['start']), intval($req['n']), $req["search"]);
|
|
||||||
$users = array_map(fn(Account $acc) => $acc->getUser(), $accounts);
|
|
||||||
return new JsonHttpResponse([
|
|
||||||
"users" => $users,
|
|
||||||
"totalCount" => $this->accounts->totalCount(),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $userId
|
|
||||||
* @return HttpResponse given user information.
|
|
||||||
*/
|
|
||||||
public function getUser(int $userId): HttpResponse {
|
|
||||||
$acc = $this->accounts->getAccount($userId);
|
|
||||||
|
|
||||||
if ($acc == null) {
|
|
||||||
return new JsonHttpResponse([ValidationFail::notFound("User not found")], HttpCodes::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonHttpResponse($acc->getUser());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addUser(): HttpResponse {
|
|
||||||
return APIControl::runChecked([
|
|
||||||
"username" => [DefaultValidators::name()],
|
|
||||||
"email" => [DefaultValidators::email()],
|
|
||||||
"password" => [DefaultValidators::password()],
|
|
||||||
"isAdmin" => [DefaultValidators::bool()],
|
|
||||||
], function (HttpRequest $req) {
|
|
||||||
$model = new AuthModel($this->accounts);
|
|
||||||
|
|
||||||
$account = $model->register($req["username"], $req["password"], $req["email"]);
|
|
||||||
if ($account == null) {
|
|
||||||
return new JsonHttpResponse([new ValidationFail("already exists", "An account with provided email ")], HttpCodes::FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonHttpResponse([
|
|
||||||
"id" => $account->getUser()->getId(),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeUsers(): HttpResponse {
|
|
||||||
return APIControl::runChecked([
|
|
||||||
"identifiers" => [DefaultValidators::array(), DefaultValidators::forall(DefaultValidators::isUnsignedInteger())],
|
|
||||||
], function (HttpRequest $req) {
|
|
||||||
$this->accounts->removeAccounts($req["identifiers"]);
|
|
||||||
return HttpResponse::fromCode(HttpCodes::OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateUser(int $id): HttpResponse {
|
|
||||||
return APIControl::runChecked([
|
|
||||||
"email" => [DefaultValidators::email()],
|
|
||||||
"username" => [DefaultValidators::name()],
|
|
||||||
"isAdmin" => [DefaultValidators::bool()],
|
|
||||||
], function (HttpRequest $req) use ($id) {
|
|
||||||
$mailAccount = $this->accounts->getAccountFromMail($req["email"]);
|
|
||||||
|
|
||||||
if ($mailAccount != null && $mailAccount->getUser()->getId() != $id) {
|
|
||||||
return new JsonHttpResponse([new ValidationFail("email exists", "The provided mail address already exists for another account.")], HttpCodes::FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->authModel->update($id, $req["email"], $req["username"], $req["isAdmin"]);
|
|
||||||
return HttpResponse::fromCode(HttpCodes::OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Api\Controller;
|
|
||||||
|
|
||||||
use IQBall\Api\APIControl;
|
|
||||||
use IQBall\App\Control;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpRequest;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|
||||||
use IQBall\Core\Model\AuthModel;
|
|
||||||
use IQBall\Core\Validation\DefaultValidators;
|
|
||||||
|
|
||||||
class APIAuthController {
|
|
||||||
private AuthModel $model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param AuthModel $model
|
|
||||||
*/
|
|
||||||
public function __construct(AuthModel $model) {
|
|
||||||
$this->model = $model;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* From given email address and password, authenticate the user and respond with its authorization token.
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function authorize(): HttpResponse {
|
|
||||||
return APIControl::runChecked([
|
|
||||||
"email" => [DefaultValidators::email(), DefaultValidators::lenBetween(5, 256)],
|
|
||||||
"password" => [DefaultValidators::password()],
|
|
||||||
], function (HttpRequest $req) {
|
|
||||||
$failures = [];
|
|
||||||
$account = $this->model->login($req["email"], $req["password"], $failures);
|
|
||||||
|
|
||||||
if (!empty($failures)) {
|
|
||||||
return new JsonHttpResponse($failures, HttpCodes::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonHttpResponse(["authorization" => $account->getToken()]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Api\Controller;
|
|
||||||
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|
||||||
|
|
||||||
class APIServerController {
|
|
||||||
private string $basePath;
|
|
||||||
private \PDO $pdo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $basePath
|
|
||||||
* @param \PDO $pdo
|
|
||||||
*/
|
|
||||||
public function __construct(string $basePath, \PDO $pdo) {
|
|
||||||
$this->basePath = $basePath;
|
|
||||||
$this->pdo = $pdo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function countLines(string $table): int {
|
|
||||||
$stmnt = $this->pdo->prepare("SELECT count(*) FROM $table");
|
|
||||||
$stmnt->execute();
|
|
||||||
$res = $stmnt->fetch(\PDO::FETCH_BOTH);
|
|
||||||
return $res[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return HttpResponse some (useless) information about the server
|
|
||||||
*/
|
|
||||||
public function getServerInfo(): HttpResponse {
|
|
||||||
|
|
||||||
return new JsonHttpResponse([
|
|
||||||
'base_path' => $this->basePath,
|
|
||||||
'date' => (int) gettimeofday(true) * 1000,
|
|
||||||
'database' => [
|
|
||||||
'accounts' => $this->countLines("Account") . " line(s)",
|
|
||||||
'tactics' => $this->countLines("Tactic") . " line(s)",
|
|
||||||
'teams' => $this->countLines("Team") . " line(s)",
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Api\Controller;
|
|
||||||
|
|
||||||
use IQBall\Api\APIControl;
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
use IQBall\Core\Data\TacticInfo;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API endpoint related to tactics
|
|
||||||
*/
|
|
||||||
class APITacticController {
|
|
||||||
private TacticModel $model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TacticModel $model
|
|
||||||
*/
|
|
||||||
public function __construct(TacticModel $model) {
|
|
||||||
$this->model = $model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* update name of tactic, specified by tactic identifier, given in url.
|
|
||||||
* @param int $tactic_id
|
|
||||||
* @param Account $account
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function updateName(int $tactic_id, Account $account): HttpResponse {
|
|
||||||
return APIControl::runChecked([
|
|
||||||
"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());
|
|
||||||
|
|
||||||
if (!empty($failures)) {
|
|
||||||
//TODO find a system to handle Unauthorized error codes more easily from failures.
|
|
||||||
return new JsonHttpResponse($failures, HttpCodes::BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HttpResponse::fromCode(HttpCodes::OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $id
|
|
||||||
* @param Account $account
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function saveContent(int $id, Account $account): HttpResponse {
|
|
||||||
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"]))) {
|
|
||||||
return new JsonHttpResponse([$fail], HttpCodes::BAD_REQUEST);
|
|
||||||
}
|
|
||||||
return HttpResponse::fromCode(HttpCodes::OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $userId
|
|
||||||
* @return HttpResponse given user information.
|
|
||||||
*/
|
|
||||||
public function getUserTactics(int $userId): HttpResponse {
|
|
||||||
$tactics = $this->model->listAllOf($userId);
|
|
||||||
|
|
||||||
$response = array_map(fn(TacticInfo $t) => [
|
|
||||||
'id' => $t->getId(),
|
|
||||||
'name' => $t->getName(),
|
|
||||||
'court' => $t->getCourtType(),
|
|
||||||
'creation_date' => $t->getCreationDate(),
|
|
||||||
|
|
||||||
], $tactics);
|
|
||||||
|
|
||||||
return new JsonHttpResponse($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Api\Controller;
|
|
||||||
|
|
||||||
use IQBall\Api\APIControl;
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
use IQBall\Core\Data\Team;
|
|
||||||
use IQBall\Core\Data\TeamInfo;
|
|
||||||
use IQBall\Core\Gateway\TeamGateway;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpRequest;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|
||||||
use IQBall\Core\Model\TeamModel;
|
|
||||||
use IQBall\Core\Validation\DefaultValidators;
|
|
||||||
|
|
||||||
class APITeamController {
|
|
||||||
private TeamModel $teamModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TeamModel $teamModel
|
|
||||||
*/
|
|
||||||
public function __construct(TeamModel $teamModel) {
|
|
||||||
$this->teamModel = $teamModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $req_params
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function listTeams(array $req_params): HttpResponse {
|
|
||||||
return APIControl::runCheckedFrom($req_params, [
|
|
||||||
'start' => [DefaultValidators::isUnsignedInteger()],
|
|
||||||
'n' => [DefaultValidators::isUnsignedInteger()],
|
|
||||||
], function (HttpRequest $req) {
|
|
||||||
$teams = $this->teamModel->listAll(intval($req['start']), intval($req['n']));
|
|
||||||
return new JsonHttpResponse([
|
|
||||||
"totalCount" => $this->teamModel->countTeam(),
|
|
||||||
"teams" => $teams,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addTeam(): HttpResponse {
|
|
||||||
return APIControl::runChecked([
|
|
||||||
'name' => [DefaultValidators::name()],
|
|
||||||
'picture' => [DefaultValidators::isURL()],
|
|
||||||
'mainColor' => [DefaultValidators::hexColor()],
|
|
||||||
'secondaryColor' => [DefaultValidators::hexColor()],
|
|
||||||
|
|
||||||
], function (HttpRequest $req) {
|
|
||||||
$this->teamModel->createTeam($req['name'], $req['picture'], $req['mainColor'], $req['secondaryColor']);
|
|
||||||
return HttpResponse::fromCode(HttpCodes::OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteTeamSelected(): HttpResponse {
|
|
||||||
return APIControl::runChecked([
|
|
||||||
'teams' => [],
|
|
||||||
], function (HttpRequest $req) {
|
|
||||||
$this->teamModel->deleteTeamSelected($req['teams']);
|
|
||||||
return HttpResponse::fromCode(HttpCodes::OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateTeam(int $id): HttpResponse {
|
|
||||||
return APIControl::runChecked([
|
|
||||||
'name' => [DefaultValidators::name()],
|
|
||||||
'picture' => [DefaultValidators::isURL()],
|
|
||||||
'mainColor' => [DefaultValidators::hexColor()],
|
|
||||||
'secondaryColor' => [DefaultValidators::hexColor()],
|
|
||||||
], function (HttpRequest $req) {
|
|
||||||
$this->teamModel->editTeam($req['id'], $req['name'], $req['picture'], $req['mainColor'], $req['secondaryColor']);
|
|
||||||
return HttpResponse::fromCode(HttpCodes::OK);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,37 @@
|
|||||||
|
import {
|
||||||
|
BrowserRouter,
|
||||||
|
createBrowserRouter,
|
||||||
|
createRoutesFromElements,
|
||||||
|
Route,
|
||||||
|
RouterProvider,
|
||||||
|
Routes
|
||||||
|
} from "react-router-dom";
|
||||||
|
|
||||||
|
import loadable from "@loadable/component";
|
||||||
|
|
||||||
|
|
||||||
|
const HomePage = loadable(() => import("./pages/Home.tsx"))
|
||||||
|
const NotFoundPage = loadable(() => import("./pages/404.tsx"))
|
||||||
|
const CreateTeamPage = loadable(() => import("./pages/CreateTeamPage.tsx"))
|
||||||
|
const TeamPanelPage = loadable(() => import("./pages/TeamPanel.tsx"))
|
||||||
|
const NewTacticPage = loadable(() => import("./pages/NewTacticPage.tsx"))
|
||||||
|
const Editor = loadable(() => import("./pages/Editor.tsx"))
|
||||||
|
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<div style={{height: "100vh", width: "100vw"}}>
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route path={"/"} element={<HomePage/>}/>
|
||||||
|
<Route path={"*"} element={<NotFoundPage/>}/>
|
||||||
|
<Route path={"team/new"} element={<CreateTeamPage/>}/>
|
||||||
|
<Route path={"team/:teamId"} element={<TeamPanelPage/>}/>
|
||||||
|
<Route path={"tactic/new"} element={<NewTacticPage/>}/>
|
||||||
|
<Route path={"tactic/new/plain"} element={<Editor courtType={"PLAIN"}/>}/>
|
||||||
|
<Route path={"tactic/new/half"} element={<Editor courtType={"HALF"}/>}/>
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,98 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App;
|
|
||||||
|
|
||||||
use IQBall\App\Session\MutableSessionHandle;
|
|
||||||
use IQBall\Core\Action;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
|
||||||
use Twig\Environment;
|
|
||||||
use Twig\Error\LoaderError;
|
|
||||||
use Twig\Error\RuntimeError;
|
|
||||||
use Twig\Error\SyntaxError;
|
|
||||||
|
|
||||||
class App {
|
|
||||||
/**
|
|
||||||
* renders (prints out) given HttpResponse to the client
|
|
||||||
* @param HttpResponse $response
|
|
||||||
* @param callable(): Environment $twigSupplier
|
|
||||||
* @return void
|
|
||||||
* @throws LoaderError
|
|
||||||
* @throws RuntimeError
|
|
||||||
* @throws SyntaxError
|
|
||||||
*/
|
|
||||||
public static function render(HttpResponse $response, callable $twigSupplier): void {
|
|
||||||
http_response_code($response->getCode());
|
|
||||||
|
|
||||||
foreach ($response->getHeaders() as $header => $value) {
|
|
||||||
header("$header: $value");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($response instanceof ViewHttpResponse) {
|
|
||||||
self::renderView($response, $twigSupplier);
|
|
||||||
} elseif ($response instanceof JsonHttpResponse) {
|
|
||||||
header('Content-type: application/json');
|
|
||||||
echo $response->getJson();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* renders (prints out) given ViewHttpResponse to the client
|
|
||||||
* @param ViewHttpResponse $response
|
|
||||||
* @param callable(): Environment $twigSupplier
|
|
||||||
* @return void
|
|
||||||
* @throws LoaderError
|
|
||||||
* @throws RuntimeError
|
|
||||||
* @throws SyntaxError
|
|
||||||
*/
|
|
||||||
private static function renderView(ViewHttpResponse $response, callable $twigSupplier): void {
|
|
||||||
$file = $response->getFile();
|
|
||||||
$args = $response->getArguments();
|
|
||||||
|
|
||||||
switch ($response->getViewKind()) {
|
|
||||||
case ViewHttpResponse::REACT_VIEW:
|
|
||||||
send_react_front($file, $args);
|
|
||||||
break;
|
|
||||||
case ViewHttpResponse::TWIG_VIEW:
|
|
||||||
try {
|
|
||||||
$twig = call_user_func($twigSupplier);
|
|
||||||
$twig->display($file, $args);
|
|
||||||
} catch (RuntimeError|SyntaxError|LoaderError $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo "There was an error rendering your view, please refer to an administrator.\nlogs date: " . date("YYYD, d M Y H:i:s");
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* run a user action, and return the generated response
|
|
||||||
* @param string $authRoute the route towards an authentication page to response with a redirection
|
|
||||||
* if the run action requires auth but session does not contain a logged-in account.
|
|
||||||
* @param Action<MutableSessionHandle> $action
|
|
||||||
* @param mixed[] $params
|
|
||||||
* @param MutableSessionHandle $session
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public static function runAction(string $authRoute, Action $action, array $params, MutableSessionHandle $session): HttpResponse {
|
|
||||||
if ($action->getAuthType() != Action::NO_AUTH) {
|
|
||||||
$account = $session->getAccount();
|
|
||||||
if ($account == null) {
|
|
||||||
// put in the session the initial url the user wanted to get
|
|
||||||
$session->setInitialTarget($_SERVER['REQUEST_URI']);
|
|
||||||
return HttpResponse::redirectAbsolute($authRoute);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($action->getAuthType() == Action::AUTH_ADMIN && !$account->getUser()->isAdmin()) {
|
|
||||||
return new JsonHttpResponse([ValidationFail::unauthorized()], HttpCodes::UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return $action->run($params, $session);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App;
|
|
||||||
|
|
||||||
use IQBall\Core\Control;
|
|
||||||
use IQBall\Core\ControlSchemaErrorResponseFactory;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpRequest;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Validation\Validator;
|
|
||||||
|
|
||||||
class AppControl {
|
|
||||||
private static function errorFactory(): ControlSchemaErrorResponseFactory {
|
|
||||||
return new class () implements ControlSchemaErrorResponseFactory {
|
|
||||||
public function apply(array $failures): HttpResponse {
|
|
||||||
return ViewHttpResponse::twig("error.html.twig", ['failures' => $failures], HttpCodes::BAD_REQUEST);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs given callback, if the request's payload json validates the given schema.
|
|
||||||
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
|
||||||
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
|
||||||
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public static function runChecked(array $schema, callable $run): HttpResponse {
|
|
||||||
return Control::runChecked($schema, $run, self::errorFactory());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs given callback, if the given request data array validates the given schema.
|
|
||||||
* @param array<string, mixed> $data the request's data array.
|
|
||||||
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
|
||||||
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
|
||||||
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public static function runCheckedFrom(array $data, array $schema, callable $run): HttpResponse {
|
|
||||||
return Control::runCheckedFrom($data, $schema, $run, self::errorFactory());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App\Controller;
|
|
||||||
|
|
||||||
use IQBall\App\Session\MutableSessionHandle;
|
|
||||||
use IQBall\App\ViewHttpResponse;
|
|
||||||
use IQBall\Core\Http\HttpRequest;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Model\AuthModel;
|
|
||||||
|
|
||||||
use IQBall\Core\Validation\DefaultValidators;
|
|
||||||
use IQBall\Core\Validation\FieldValidationFail;
|
|
||||||
|
|
||||||
class AuthController {
|
|
||||||
private AuthModel $model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param AuthModel $model
|
|
||||||
*/
|
|
||||||
public function __construct(AuthModel $model) {
|
|
||||||
$this->model = $model;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function displayRegister(): HttpResponse {
|
|
||||||
return ViewHttpResponse::twig("display_register.html.twig", []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* registers given account
|
|
||||||
* @param mixed[] $requestData
|
|
||||||
* @param MutableSessionHandle $session
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function register(array $requestData, MutableSessionHandle $session): HttpResponse {
|
|
||||||
$fails = [];
|
|
||||||
$request = HttpRequest::from($requestData, $fails, [
|
|
||||||
"username" => [DefaultValidators::name(), DefaultValidators::lenBetween(2, 32)],
|
|
||||||
"password" => [DefaultValidators::password()],
|
|
||||||
"confirmpassword" => [DefaultValidators::password()],
|
|
||||||
"email" => [DefaultValidators::email(), DefaultValidators::lenBetween(5, 256)],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!empty($fails)) {
|
|
||||||
return ViewHttpResponse::twig("display_register.html.twig", ['fails' => $fails, 'username' => $requestData['username'], 'email' => $requestData['email'], 'password' => $requestData['password'], 'confirmpassword' => $requestData['confirmpassword'], 'accept' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request["password"] != $request['confirmpassword']) {
|
|
||||||
$fails[] = new FieldValidationFail("confirmpassword", "Le mot de passe et la confirmation ne sont pas les mêmes.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$account = $this->model->register($request['username'], $request["password"], $request['email']);
|
|
||||||
|
|
||||||
if (!$account) {
|
|
||||||
$fails[] = new FieldValidationFail("email", "L'email existe déjà");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($fails)) {
|
|
||||||
return ViewHttpResponse::twig("display_register.html.twig", ['fails' => $fails, 'password' => $requestData['password'], 'confirmpassword' => $requestData['confirmpassword'], 'accept' => true]);
|
|
||||||
}
|
|
||||||
$session->setAccount($account);
|
|
||||||
|
|
||||||
$target_url = $session->getInitialTarget();
|
|
||||||
if ($target_url != null) {
|
|
||||||
return HttpResponse::redirectAbsolute($target_url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HttpResponse::redirect("/home");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function displayLogin(): HttpResponse {
|
|
||||||
return ViewHttpResponse::twig("display_login.html.twig", []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* logins given account credentials
|
|
||||||
* @param mixed[] $request
|
|
||||||
* @param MutableSessionHandle $session
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function login(array $request, MutableSessionHandle $session): HttpResponse {
|
|
||||||
$fails = [];
|
|
||||||
$account = $this->model->login($request['email'], $request['password'], $fails);
|
|
||||||
if (!empty($fails)) {
|
|
||||||
return ViewHttpResponse::twig("display_login.html.twig", ['fails' => $fails, 'password' => $request['password'], 'email' => $request['email']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$session->setAccount($account);
|
|
||||||
|
|
||||||
$target_url = $session->getInitialTarget();
|
|
||||||
$session->setInitialTarget(null);
|
|
||||||
if ($target_url != null) {
|
|
||||||
return HttpResponse::redirectAbsolute($target_url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HttpResponse::redirect("/home");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App\Controller;
|
|
||||||
|
|
||||||
use IQBall\App\Session\SessionHandle;
|
|
||||||
use IQBall\App\Validator\TacticValidator;
|
|
||||||
use IQBall\App\ViewHttpResponse;
|
|
||||||
use IQBall\Core\Data\CourtType;
|
|
||||||
use IQBall\Core\Data\TacticInfo;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Model\TacticModel;
|
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
|
||||||
|
|
||||||
class EditorController {
|
|
||||||
private TacticModel $model;
|
|
||||||
|
|
||||||
public function __construct(TacticModel $model) {
|
|
||||||
$this->model = $model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TacticInfo $tactic
|
|
||||||
* @return ViewHttpResponse the editor view for given tactic
|
|
||||||
*/
|
|
||||||
private function openEditorFor(TacticInfo $tactic): ViewHttpResponse {
|
|
||||||
return ViewHttpResponse::react("views/Editor.tsx", [
|
|
||||||
"id" => $tactic->getId(),
|
|
||||||
"name" => $tactic->getName(),
|
|
||||||
"content" => $tactic->getContent(),
|
|
||||||
"courtType" => $tactic->getCourtType()->name(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createNew(): ViewHttpResponse {
|
|
||||||
return ViewHttpResponse::react("views/NewTacticPanel.tsx", []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return ViewHttpResponse the editor view for a test tactic.
|
|
||||||
*/
|
|
||||||
private function openTestEditor(CourtType $courtType): ViewHttpResponse {
|
|
||||||
return ViewHttpResponse::react("views/Editor.tsx", [
|
|
||||||
"id" => -1, //-1 id means that the editor will not support saves
|
|
||||||
"name" => TacticModel::TACTIC_DEFAULT_NAME,
|
|
||||||
"content" => '{"components": []}',
|
|
||||||
"courtType" => $courtType->name(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a new empty tactic, with default name
|
|
||||||
* If the given session does not contain a connected account,
|
|
||||||
* open a test editor.
|
|
||||||
* @param SessionHandle $session
|
|
||||||
* @param CourtType $type
|
|
||||||
* @return ViewHttpResponse the editor view
|
|
||||||
*/
|
|
||||||
public function createNewOfKind(CourtType $type, SessionHandle $session): ViewHttpResponse {
|
|
||||||
|
|
||||||
$action = $session->getAccount();
|
|
||||||
|
|
||||||
if ($action == null) {
|
|
||||||
return $this->openTestEditor($type);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tactic = $this->model->makeNewDefault($session->getAccount()->getUser()->getId(), $type);
|
|
||||||
return $this->openEditorFor($tactic);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns an editor view for a given tactic
|
|
||||||
* @param int $id the targeted tactic identifier
|
|
||||||
* @param SessionHandle $session
|
|
||||||
* @return ViewHttpResponse
|
|
||||||
*/
|
|
||||||
public function openEditor(int $id, SessionHandle $session): ViewHttpResponse {
|
|
||||||
$tactic = $this->model->get($id);
|
|
||||||
|
|
||||||
$failure = TacticValidator::validateAccess($id, $tactic, $session->getAccount()->getUser()->getId());
|
|
||||||
|
|
||||||
if ($failure != null) {
|
|
||||||
return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->openEditorFor($tactic);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,246 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
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\DefaultValidators;
|
|
||||||
|
|
||||||
class TeamController {
|
|
||||||
private TeamModel $model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TeamModel $model
|
|
||||||
*/
|
|
||||||
public function __construct(TeamModel $model) {
|
|
||||||
$this->model = $model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param SessionHandle $session
|
|
||||||
* @return ViewHttpResponse the team creation panel
|
|
||||||
*/
|
|
||||||
public function displayCreateTeam(SessionHandle $session): ViewHttpResponse {
|
|
||||||
return ViewHttpResponse::twig("insert_team.html.twig", []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param SessionHandle $session
|
|
||||||
* @return ViewHttpResponse the team panel to delete a member
|
|
||||||
*/
|
|
||||||
public function displayDeleteMember(SessionHandle $session): ViewHttpResponse {
|
|
||||||
return ViewHttpResponse::twig("delete_member.html.twig", []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create a new team from given request name, mainColor, secondColor and picture url
|
|
||||||
* @param array<string, mixed> $request
|
|
||||||
* @param SessionHandle $session
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function submitTeam(array $request, SessionHandle $session): HttpResponse {
|
|
||||||
$failures = [];
|
|
||||||
$request = HttpRequest::from($request, $failures, [
|
|
||||||
"name" => [DefaultValidators::lenBetween(1, 32), DefaultValidators::nameWithSpaces()],
|
|
||||||
"main_color" => [DefaultValidators::hexColor()],
|
|
||||||
"second_color" => [DefaultValidators::hexColor()],
|
|
||||||
"picture" => [DefaultValidators::isURL()],
|
|
||||||
]);
|
|
||||||
if (!empty($failures)) {
|
|
||||||
$badFields = [];
|
|
||||||
foreach ($failures as $e) {
|
|
||||||
if ($e instanceof FieldValidationFail) {
|
|
||||||
$badFields[] = $e->getFieldName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ViewHttpResponse::twig('insert_team.html.twig', ['bad_fields' => $badFields]);
|
|
||||||
}
|
|
||||||
$teamId = $this->model->createTeam($request['name'], $request['picture'], $request['main_color'], $request['second_color']);
|
|
||||||
$this->model->addMember($session->getAccount()->getUser()->getEmail(), $teamId, 'COACH');
|
|
||||||
return HttpResponse::redirect('/team/' . $teamId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param SessionHandle $session
|
|
||||||
* @return ViewHttpResponse the panel to search a team by its name
|
|
||||||
*/
|
|
||||||
public function displayListTeamByName(SessionHandle $session): ViewHttpResponse {
|
|
||||||
return ViewHttpResponse::twig("list_team_by_name.html.twig", []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns a view that contains all the teams description whose name matches the given name needle.
|
|
||||||
* @param array<string, mixed> $request
|
|
||||||
* @param SessionHandle $session
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function listTeamByName(array $request, SessionHandle $session): HttpResponse {
|
|
||||||
$errors = [];
|
|
||||||
$request = HttpRequest::from($request, $errors, [
|
|
||||||
"name" => [DefaultValidators::lenBetween(1, 32), DefaultValidators::nameWithSpaces()],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!empty($errors) && $errors[0] instanceof FieldValidationFail) {
|
|
||||||
$badField = $errors[0]->getFieldName();
|
|
||||||
return ViewHttpResponse::twig('list_team_by_name.html.twig', ['bad_field' => $badField]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$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, $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(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, [
|
|
||||||
"email" => [DefaultValidators::email(), DefaultValidators::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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $idTeam
|
|
||||||
* @param SessionHandle $session
|
|
||||||
* @return ViewHttpResponse
|
|
||||||
*/
|
|
||||||
public function displayEditTeam(int $idTeam, SessionHandle $session): ViewHttpResponse {
|
|
||||||
return ViewHttpResponse::twig("edit_team.html.twig", ['team' => $this->model->getTeam($idTeam, $session->getAccount()->getUser()->getId())]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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" => [DefaultValidators::lenBetween(1, 32), DefaultValidators::nameWithSpaces()],
|
|
||||||
"main_color" => [DefaultValidators::hexColor()],
|
|
||||||
"second_color" => [DefaultValidators::hexColor()],
|
|
||||||
"picture" => [DefaultValidators::isURL()],
|
|
||||||
]);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App\Controller;
|
|
||||||
|
|
||||||
use IQBall\App\Session\MutableSessionHandle;
|
|
||||||
use IQBall\App\Session\SessionHandle;
|
|
||||||
use IQBall\App\ViewHttpResponse;
|
|
||||||
use IQBall\Core\Gateway\AccountGateway;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Model\TacticModel;
|
|
||||||
use IQBall\Core\Model\TeamModel;
|
|
||||||
|
|
||||||
class UserController {
|
|
||||||
private TacticModel $tactics;
|
|
||||||
private ?TeamModel $teams;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TacticModel $tactics
|
|
||||||
* @param TeamModel|null $teams
|
|
||||||
*/
|
|
||||||
public function __construct(TacticModel $tactics, ?TeamModel $teams = null) {
|
|
||||||
$this->tactics = $tactics;
|
|
||||||
$this->teams = $teams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param SessionHandle $session
|
|
||||||
* @return ViewHttpResponse the home page view
|
|
||||||
*/
|
|
||||||
public function home(SessionHandle $session): ViewHttpResponse {
|
|
||||||
$limitNbTactics = 5;
|
|
||||||
|
|
||||||
$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($user->getId());
|
|
||||||
} else {
|
|
||||||
$teams = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return ViewHttpResponse::react("views/Home.tsx", [
|
|
||||||
"lastTactics" => $lastTactics,
|
|
||||||
"allTactics" => $allTactics,
|
|
||||||
"teams" => $teams,
|
|
||||||
"username" => $name,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return ViewHttpResponse account settings page
|
|
||||||
*/
|
|
||||||
public function settings(SessionHandle $session): ViewHttpResponse {
|
|
||||||
return ViewHttpResponse::react("views/Settings.tsx", []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function disconnect(MutableSessionHandle $session): HttpResponse {
|
|
||||||
$session->destroy();
|
|
||||||
return HttpResponse::redirect("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App\Controller;
|
|
||||||
|
|
||||||
use IQBall\App\Session\SessionHandle;
|
|
||||||
use IQBall\App\Validator\TacticValidator;
|
|
||||||
use IQBall\App\ViewHttpResponse;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Model\TacticModel;
|
|
||||||
|
|
||||||
class VisualizerController {
|
|
||||||
private TacticModel $tacticModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TacticModel $tacticModel
|
|
||||||
*/
|
|
||||||
public function __construct(TacticModel $tacticModel) {
|
|
||||||
$this->tacticModel = $tacticModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a visualisation page for the tactic specified by its identifier in the url.
|
|
||||||
* @param int $id
|
|
||||||
* @param SessionHandle $session
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function openVisualizer(int $id, SessionHandle $session): HttpResponse {
|
|
||||||
$tactic = $this->tacticModel->get($id);
|
|
||||||
|
|
||||||
$failure = TacticValidator::validateAccess($id, $tactic, $session->getAccount()->getUser()->getId());
|
|
||||||
|
|
||||||
if ($failure != null) {
|
|
||||||
return ViewHttpResponse::twig('error.html.twig', ['failures' => [$failure]], HttpCodes::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ViewHttpResponse::react("views/Visualizer.tsx", ["name" => $tactic->getName()]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App\Session;
|
|
||||||
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The mutable side of a session handle
|
|
||||||
*/
|
|
||||||
interface MutableSessionHandle extends SessionHandle {
|
|
||||||
/**
|
|
||||||
* @param string|null $url the url to redirect the user to after authentication.
|
|
||||||
*/
|
|
||||||
public function setInitialTarget(?string $url): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Account $account update the session's account
|
|
||||||
*/
|
|
||||||
public function setAccount(Account $account): void;
|
|
||||||
|
|
||||||
public function destroy(): void;
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App\Session;
|
|
||||||
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A PHP session handle
|
|
||||||
*/
|
|
||||||
class PhpSessionHandle implements MutableSessionHandle {
|
|
||||||
public static function init(): self {
|
|
||||||
if (session_status() !== PHP_SESSION_NONE) {
|
|
||||||
throw new \Exception("A php session is already started !");
|
|
||||||
}
|
|
||||||
session_start();
|
|
||||||
return new PhpSessionHandle();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAccount(): ?Account {
|
|
||||||
return $_SESSION["account"] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInitialTarget(): ?string {
|
|
||||||
return $_SESSION["target"] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setAccount(Account $account): void {
|
|
||||||
$_SESSION["account"] = $account;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setInitialTarget(?string $url): void {
|
|
||||||
$_SESSION["target"] = $url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function destroy(): void {
|
|
||||||
session_destroy();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App\Session;
|
|
||||||
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An immutable session handle
|
|
||||||
*/
|
|
||||||
interface SessionHandle {
|
|
||||||
/**
|
|
||||||
* The initial target url if the user wanted to perform an action that requires authentication
|
|
||||||
* but has been required to login first in the application.
|
|
||||||
* @return string|null Get the initial targeted URL
|
|
||||||
*/
|
|
||||||
public function getInitialTarget(): ?string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The session account if the user is logged in.
|
|
||||||
* @return Account|null
|
|
||||||
*/
|
|
||||||
public function getAccount(): ?Account;
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App\Validator;
|
|
||||||
|
|
||||||
use IQBall\Core\Data\TacticInfo;
|
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
|
||||||
|
|
||||||
class TacticValidator {
|
|
||||||
public static function validateAccess(int $tacticId, ?TacticInfo $tactic, int $ownerId): ?ValidationFail {
|
|
||||||
if ($tactic == null) {
|
|
||||||
return ValidationFail::notFound("La tactique $tacticId n'existe pas");
|
|
||||||
}
|
|
||||||
if ($tactic->getOwnerId() != $ownerId) {
|
|
||||||
return ValidationFail::unauthorized("Vous ne pouvez pas accéder à cette tactique.");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\App;
|
|
||||||
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
|
|
||||||
class ViewHttpResponse extends HttpResponse {
|
|
||||||
public const TWIG_VIEW = 0;
|
|
||||||
public const REACT_VIEW = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string File path of the responded view
|
|
||||||
*/
|
|
||||||
private string $file;
|
|
||||||
/**
|
|
||||||
* @var array<string, mixed> View arguments
|
|
||||||
*/
|
|
||||||
private array $arguments;
|
|
||||||
/**
|
|
||||||
* @var int Kind of view, see {@link self::TWIG_VIEW} and {@link self::REACT_VIEW}
|
|
||||||
*/
|
|
||||||
private int $kind;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $code
|
|
||||||
* @param int $kind
|
|
||||||
* @param string $file
|
|
||||||
* @param array<string, mixed> $arguments
|
|
||||||
*/
|
|
||||||
private function __construct(int $kind, string $file, array $arguments, int $code = HttpCodes::OK) {
|
|
||||||
parent::__construct($code, []);
|
|
||||||
$this->kind = $kind;
|
|
||||||
$this->file = $file;
|
|
||||||
$this->arguments = $arguments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getViewKind(): int {
|
|
||||||
return $this->kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFile(): string {
|
|
||||||
return $this->file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
public function getArguments(): array {
|
|
||||||
return $this->arguments;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a twig view response
|
|
||||||
* @param string $file
|
|
||||||
* @param array<string, mixed> $arguments
|
|
||||||
* @param int $code
|
|
||||||
* @return ViewHttpResponse
|
|
||||||
*/
|
|
||||||
public static function twig(string $file, array $arguments, int $code = HttpCodes::OK): ViewHttpResponse {
|
|
||||||
return new ViewHttpResponse(self::TWIG_VIEW, $file, $arguments, $code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a react view response
|
|
||||||
* @param string $file
|
|
||||||
* @param array<string, mixed> $arguments
|
|
||||||
* @param int $code
|
|
||||||
* @return ViewHttpResponse
|
|
||||||
*/
|
|
||||||
public static function react(string $file, array $arguments, int $code = HttpCodes::OK): ViewHttpResponse {
|
|
||||||
return new ViewHttpResponse(self::REACT_VIEW, $file, $arguments, $code);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport"
|
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>Paramètres</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
padding-left: 10%;
|
|
||||||
padding-right: 10%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<button onclick="location.pathname='{{ path('/home') }}'">Retour</button>
|
|
||||||
<h1>Paramètres</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,118 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Ajouter un membre</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"],
|
|
||||||
input[type="radio"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.role {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio {
|
|
||||||
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/#{idTeam}/addMember") }}" 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 %}
|
|
||||||
{% 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="PLAYER" checked />
|
|
||||||
</div>
|
|
||||||
<div class="radio">
|
|
||||||
<label for="C">Coach</label>
|
|
||||||
<input type="radio" id="C" name="role" value="COACH" />
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="submit" value="Confirmer">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,73 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Ajouter un membre</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
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>Supprimez un membre de votre équipe</h2>
|
|
||||||
<form action="{{ path('/team/members/remove') }}" method="POST">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="team">Team où supprimer 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>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="submit" value="Confirmer">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,46 +0,0 @@
|
|||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Profil Utilisateur</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: start;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-profile {
|
|
||||||
background-color: #7FBFFF;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
max-width: 400px;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="user-profile">
|
|
||||||
<h1>Votre profil</h1>
|
|
||||||
<p><strong>Pseudo : </strong> {{ username }} </p>
|
|
||||||
<p><strong>Email : {{ email }} </strong></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,108 +0,0 @@
|
|||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Connexion</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"], input[type="password"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.error-messages {
|
|
||||||
color: #ff331a;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
{% for err in fails %}
|
|
||||||
.form-group
|
|
||||||
|
|
||||||
#
|
|
||||||
{{ err.getFieldName() }}
|
|
||||||
{
|
|
||||||
border-color: red
|
|
||||||
;
|
|
||||||
}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
.inscr {
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
#buttons{
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 10px 20px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.button{
|
|
||||||
background-color: #007bff;
|
|
||||||
color: #fff;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover{
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<center><h2>Se connecter</h2></center>
|
|
||||||
<form action="{{ path('/login') }}" method="post">
|
|
||||||
<div class="form-group">
|
|
||||||
|
|
||||||
{% for name in fails %}
|
|
||||||
<label class="error-messages"> {{ name.getMessage() }} </label>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<label for="email">Email :</label>
|
|
||||||
<input type="text" id="email" name="email" required value="{{ email }}">
|
|
||||||
<label for="password">Mot de passe :</label>
|
|
||||||
<input type="password" id="password" name="password" required value="{{ password }}">
|
|
||||||
<a href="{{ path('/register') }}" class="inscr">Vous n'avez pas de compte ?</a>
|
|
||||||
<br><br>
|
|
||||||
<div id = "buttons">
|
|
||||||
<input class = "button" type="submit" value="Se connecter">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,123 +0,0 @@
|
|||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>S'enregistrer</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"], input[type="password"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-messages {
|
|
||||||
color: #ff331a;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{% for err in fails %}
|
|
||||||
.form-group
|
|
||||||
|
|
||||||
#
|
|
||||||
{{ err.getFieldName() }}
|
|
||||||
{
|
|
||||||
border-color: red
|
|
||||||
;
|
|
||||||
}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
.inscr{
|
|
||||||
font-size: small;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.consentement{
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
#buttons{
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 10px 20px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.button{
|
|
||||||
background-color: #007bff;
|
|
||||||
color: #fff;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover{
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<center><h2>S'enregistrer</h2></center>
|
|
||||||
<form action="{{ path('/register') }}" method="post">
|
|
||||||
<div class="form-group">
|
|
||||||
|
|
||||||
{% for name in fails %}
|
|
||||||
<label class="error-messages"> {{ name.getFieldName() }} : {{ name.getMessage() }} </label>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<label for="username">Nom d'utilisateur :</label>
|
|
||||||
<input type="text" id="username" name="username" value="{{ username }}"required>
|
|
||||||
<label for="password">Mot de passe :</label>
|
|
||||||
<input type="password" id="password" name="password" required value="{{ password }}">
|
|
||||||
<label for="confirmpassword">Confirmer le mot de passe :</label>
|
|
||||||
<input type="password" id="confirmpassword" name="confirmpassword" required value="{{ confirmpassword }}">
|
|
||||||
<label for="email">Email :</label>
|
|
||||||
<input type="text" id="email" name="email" value="{{ email }}"required><br><br>
|
|
||||||
<label class="consentement">
|
|
||||||
<input type="checkbox" name="consentement" required checked="{{ accepted }}">
|
|
||||||
En cochant cette case, j'accepte que mes données personnelles, tel que mon adresse e-mail, soient collectées et traitées conformément à la politique de confidentialité de Sportify.
|
|
||||||
</label><br>
|
|
||||||
<a href="{{ path('/login') }}" class="inscr">Vous avez déjà un compte ?</a>
|
|
||||||
</div>
|
|
||||||
<div id = "buttons">
|
|
||||||
<input class = "button" type="submit" value="Créer votre compte">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,18 +0,0 @@
|
|||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Twig view</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1>Hello world</h1>
|
|
||||||
|
|
||||||
|
|
||||||
{% for v in results %}
|
|
||||||
<p>username: {{ v.name }}</p>
|
|
||||||
<p>description: {{ v.description }}</p>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,109 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Twig view</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.square {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#main_color {
|
|
||||||
border: solid;
|
|
||||||
background-color: {{ team.getInfo().getMainColor().getValue() }};
|
|
||||||
}
|
|
||||||
|
|
||||||
#second_color {
|
|
||||||
background-color: {{ team.getInfo().getSecondColor().getValue() }};
|
|
||||||
border: solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
background-color: #fff;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 60%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#colors{
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
.color {
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 80px;
|
|
||||||
width: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#delete{
|
|
||||||
border-radius:10px ;
|
|
||||||
background-color: red;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player{
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1><a href="{{ path('/') }}">IQBall</a></h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<section class="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 id="colors">
|
|
||||||
<div class="color"><p>Couleur principale : </p>
|
|
||||||
<div class="square" id="main_color"></div>
|
|
||||||
</div>
|
|
||||||
<div class="color"><p>Couleur secondaire : </p>
|
|
||||||
<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() %}
|
|
||||||
<div class="player">
|
|
||||||
<p> {{ m.getUserId() }} </p>
|
|
||||||
{% if m.getRole().isCoach() %}
|
|
||||||
<p> : Coach</p>
|
|
||||||
{% else %}
|
|
||||||
<p> : Joueur</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div>
|
|
||||||
<h3>Cette équipe ne peut être affichée</h3>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</section>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,61 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<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">
|
|
||||||
<h2>Chercher une équipe</h2>
|
|
||||||
<form action="{{ path('/team/search') }}" method="post">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="name">Nom de l'équipe :</label>
|
|
||||||
<input type="text" id="name" name="name" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="submit" value="Confirmer">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
{% for t in teams %}
|
|
||||||
<div class="team" onclick="window.location.href = '{{ 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>
|
|
@ -1,81 +0,0 @@
|
|||||||
<!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>
|
|
@ -1,57 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Error</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #da6110;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 15px
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
margin-top: 15px
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
display: block;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 2px solid #da6110;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover {
|
|
||||||
background-color: #da6110
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h1>IQBall</h1>
|
|
||||||
|
|
||||||
{% for fail in failures %}
|
|
||||||
<h2>{{ fail.getKind() }} : {{ fail.getMessage() }}</h2>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
|
||||||
<button class="button" onclick="location.href='{{ path('/home') }}'" type="button">Retour à la page d'accueil</button>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,97 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport"
|
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>Page d'accueil</title>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
padding-left: 10%;
|
|
||||||
padding-right: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#bandeau {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
#bandeau > h1 {
|
|
||||||
self-align: center;
|
|
||||||
padding: 0%;
|
|
||||||
margin: 0%;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#account {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#account:hover {
|
|
||||||
background-color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
#account img {
|
|
||||||
width: 70%;
|
|
||||||
height: auto;
|
|
||||||
align-self: center;
|
|
||||||
padding: 5%;
|
|
||||||
margin: 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#account p {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<button onclick="location.pathname='{{ path('/disconnect') }}'"> Se déconnecter</button>
|
|
||||||
<div id="bandeau">
|
|
||||||
<h1>IQ CourtObjects</h1>
|
|
||||||
<div id="account" onclick="location.pathname='{{ path('/settings') }}'">
|
|
||||||
<img
|
|
||||||
src="{{ path('/assets/icon/account.svg') }}"
|
|
||||||
alt="Account logo"
|
|
||||||
/>
|
|
||||||
<p>Mon profil
|
|
||||||
<p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Mes équipes</h2>
|
|
||||||
|
|
||||||
<button onclick="location.pathname='{{ path('/team/new') }}'"> Créer une nouvelle équipe</button>
|
|
||||||
|
|
||||||
{% if recentTeam != null %}
|
|
||||||
{% for team in recentTeam %}
|
|
||||||
<div>
|
|
||||||
<p> {{ team.name }} </p>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<p>Aucune équipe créée !</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h2> Mes strategies </h2>
|
|
||||||
|
|
||||||
<button onclick="location.pathname='{{ path('/tactic/new') }}'"> Créer une nouvelle tactique</button>
|
|
||||||
|
|
||||||
{% if recentTactic != null %}
|
|
||||||
{% for tactic in recentTactic %}
|
|
||||||
<div onclick="location.pathname='{{ path("/tactic/#{strategie.id}/edit") }}'">
|
|
||||||
<p> {{ tactic.id }} - {{ tactic.name }} - {{ tactic.creation_date }} </p>
|
|
||||||
<button onclick="location.pathname='{{ path("/tactic/#{tactic.id}/edit") }}'"> Editer la
|
|
||||||
stratégie {{ tactic.id }} </button>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<p> Aucune tactique créée !</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,81 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Insertion view</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
{% for item in bad_fields %}
|
|
||||||
#{{ item }}{
|
|
||||||
border-color: red;
|
|
||||||
}{% endfor %} input[type="text"], input[type="password"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="submit"] {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: #fff;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="submit"]:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<h2>Créer une équipe</h2>
|
|
||||||
<form action="{{ path('/team/new') }}" method="post">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="name">Nom de l'équipe :</label>
|
|
||||||
<input type="text" id="name" name="name" required>
|
|
||||||
<label for="picture">Logo:</label>
|
|
||||||
<input type="text" id="picture" name="picture" required>
|
|
||||||
<label for="main_color">Couleur principale</label>
|
|
||||||
<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>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="submit" value="Confirmer">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,79 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Insertion view</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
{% for item in bad_fields %}
|
|
||||||
#{{ item }}{
|
|
||||||
border-color: red;
|
|
||||||
}{% endfor %} input[type="text"], input[type="password"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="submit"] {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: #fff;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="submit"]:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1><a href="{{ path('/') }}">IQBall</a></h1>
|
|
||||||
</header>
|
|
||||||
<div class="container">
|
|
||||||
<h2>Chercher une équipe</h2>
|
|
||||||
<form action="{{ path('/team/search') }}" method="post">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="name">Nom de l'équipe :</label>
|
|
||||||
<input type="text" id="name" name="name" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="submit" value="Confirmer">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,58 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<script type="module">
|
|
||||||
<?php
|
|
||||||
if (SUPPORTS_FAST_REFRESH) {
|
|
||||||
$asset_server = asset("");
|
|
||||||
echo "
|
|
||||||
import RefreshRuntime from '{$asset_server}front/@react-refresh'
|
|
||||||
RefreshRuntime.injectIntoGlobalHook(window)
|
|
||||||
window.\$RefreshReg$ = () => {}
|
|
||||||
window.\$RefreshSig$ = () => (type) => type
|
|
||||||
window.__vite_plugin_react_preamble_installed__ = true
|
|
||||||
";
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<link rel="icon" href="<?= asset("assets/favicon.ico") ?>">
|
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport"
|
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
<title>Document</title>
|
|
||||||
|
|
||||||
<!-- remove default screen margin,
|
|
||||||
html and body to take full screen size -->
|
|
||||||
<style>
|
|
||||||
body, html, #root {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="root"></div>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
here's the magic.
|
|
||||||
imports the given view URL, and assume that the view exports a function named `Component`.
|
|
||||||
see ViewRenderer.tsx::renderView for more info
|
|
||||||
-->
|
|
||||||
<script type="module">
|
|
||||||
import {renderView} from "<?= asset("ViewRenderer.tsx") ?>"
|
|
||||||
import Component from "<?= asset($url) ?>"
|
|
||||||
renderView(Component, <?= json_encode($arguments) ?>)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sends a react view to the user client.
|
|
||||||
* @param string $url url of the react file to render
|
|
||||||
* @param array<string, mixed> $arguments arguments to pass to the rendered react component
|
|
||||||
* The arguments must be a json-encodable key/value dictionary.
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
function send_react_front(string $url, array $arguments) {
|
|
||||||
// the $url and $argument values are used into the included file
|
|
||||||
require_once "react-display-file.php";
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core;
|
|
||||||
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represent an action.
|
|
||||||
* @template S session
|
|
||||||
*/
|
|
||||||
class Action {
|
|
||||||
public const NO_AUTH = 1;
|
|
||||||
public const AUTH_USER = 2;
|
|
||||||
public const AUTH_ADMIN = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var callable(mixed[], S): HttpResponse $action action to call
|
|
||||||
*/
|
|
||||||
protected $action;
|
|
||||||
|
|
||||||
private int $authType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(mixed[], S): HttpResponse $action
|
|
||||||
*/
|
|
||||||
protected function __construct(callable $action, int $authType) {
|
|
||||||
$this->action = $action;
|
|
||||||
$this->authType = $authType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAuthType(): int {
|
|
||||||
return $this->authType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs an action
|
|
||||||
* @param mixed[] $params
|
|
||||||
* @param S $session
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function run(array $params, $session): HttpResponse {
|
|
||||||
$params = array_values($params);
|
|
||||||
$params[] = $session;
|
|
||||||
return call_user_func_array($this->action, $params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(mixed[], S): HttpResponse $action
|
|
||||||
* @return Action<S> an action that does not require to have an authorization.
|
|
||||||
*/
|
|
||||||
public static function noAuth(callable $action): Action {
|
|
||||||
return new Action($action, self::NO_AUTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(mixed[], S): HttpResponse $action
|
|
||||||
* @return Action<S> an action that does require to have an authorization.
|
|
||||||
*/
|
|
||||||
public static function auth(callable $action): Action {
|
|
||||||
return new Action($action, self::AUTH_USER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(mixed[], S): HttpResponse $action
|
|
||||||
* @return Action<S> an action that does require to have an authorization, and to be an administrator.
|
|
||||||
*/
|
|
||||||
public static function admin(callable $action): Action {
|
|
||||||
return new Action($action, self::AUTH_ADMIN);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core;
|
|
||||||
|
|
||||||
use PDO;
|
|
||||||
|
|
||||||
class Connection {
|
|
||||||
private PDO $pdo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param PDO $pdo
|
|
||||||
*/
|
|
||||||
public function __construct(PDO $pdo) {
|
|
||||||
$this->pdo = $pdo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function lastInsertId(): string {
|
|
||||||
return $this->pdo->lastInsertId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* execute a request
|
|
||||||
* @param string $query
|
|
||||||
* @param array<string, array<mixed, int>> $args
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function exec(string $query, array $args) {
|
|
||||||
$stmnt = $this->prep($query, $args);
|
|
||||||
$stmnt->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a request, and return the returned rows
|
|
||||||
* @param string $query the SQL request
|
|
||||||
* @param array<string, array<mixed, int>> $args an array containing the arguments label, value and type: ex: `[":label" => [$value, PDO::PARAM_TYPE]`
|
|
||||||
* @return array<string, mixed>[] the returned rows of the request
|
|
||||||
*/
|
|
||||||
public function fetch(string $query, array $args): array {
|
|
||||||
$stmnt = $this->prep($query, $args);
|
|
||||||
$stmnt->execute();
|
|
||||||
return $stmnt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $query
|
|
||||||
* @param array<string, array<mixed, int>> $args
|
|
||||||
* @return \PDOStatement
|
|
||||||
*/
|
|
||||||
private function prep(string $query, array $args): \PDOStatement {
|
|
||||||
$stmnt = $this->pdo->prepare($query);
|
|
||||||
foreach ($args as $name => $value) {
|
|
||||||
$stmnt->bindValue($name, $value[0], $value[1]);
|
|
||||||
}
|
|
||||||
return $stmnt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function prepare(string $query): \PDOStatement {
|
|
||||||
return $this->pdo->prepare($query);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core;
|
|
||||||
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\HttpRequest;
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
|
||||||
use IQBall\Core\Validation\Validator;
|
|
||||||
|
|
||||||
class Control {
|
|
||||||
/**
|
|
||||||
* Runs given callback, if the request's payload json validates the given schema.
|
|
||||||
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
|
||||||
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
|
||||||
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
|
||||||
* @param ControlSchemaErrorResponseFactory $errorFactory an error factory to use if the request does not validate the required schema
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public static function runChecked(array $schema, callable $run, ControlSchemaErrorResponseFactory $errorFactory): HttpResponse {
|
|
||||||
$request_body = file_get_contents('php://input');
|
|
||||||
$payload_obj = json_decode($request_body);
|
|
||||||
if (!$payload_obj instanceof \stdClass) {
|
|
||||||
$fail = new ValidationFail("bad-payload", "request body is not a valid json object");
|
|
||||||
return $errorFactory->apply([$fail]);
|
|
||||||
|
|
||||||
}
|
|
||||||
$payload = get_object_vars($payload_obj);
|
|
||||||
return self::runCheckedFrom($payload, $schema, $run, $errorFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs given callback, if the given request data array validates the given schema.
|
|
||||||
* @param array<string, mixed> $data the request's data array.
|
|
||||||
* @param array<string, Validator[]> $schema an array of `fieldName => DefaultValidators` which represents the request object schema
|
|
||||||
* @param callable(HttpRequest): HttpResponse $run the callback to run if the request is valid according to the given schema.
|
|
||||||
* The callback must accept an HttpRequest, and return an HttpResponse object.
|
|
||||||
* @param ControlSchemaErrorResponseFactory $errorFactory an error factory to use if the request does not validate the required schema
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public static function runCheckedFrom(array $data, array $schema, callable $run, ControlSchemaErrorResponseFactory $errorFactory): HttpResponse {
|
|
||||||
$fails = [];
|
|
||||||
$request = HttpRequest::from($data, $fails, $schema);
|
|
||||||
|
|
||||||
if (!empty($fails)) {
|
|
||||||
return $errorFactory->apply($fails);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return call_user_func_array($run, [$request]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core;
|
|
||||||
|
|
||||||
use IQBall\Core\Http\HttpResponse;
|
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
|
||||||
|
|
||||||
interface ControlSchemaErrorResponseFactory {
|
|
||||||
/**
|
|
||||||
* @param ValidationFail[] $failures
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public function apply(array $failures): HttpResponse;
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class of a user account.
|
|
||||||
* Contains the private information that we don't want
|
|
||||||
* to share to other users, or non-needed public information
|
|
||||||
*/
|
|
||||||
class Account {
|
|
||||||
/**
|
|
||||||
* @var string string token
|
|
||||||
*/
|
|
||||||
private string $token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var User contains all the account's "public" information
|
|
||||||
*/
|
|
||||||
private User $user;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $token
|
|
||||||
* @param User $user
|
|
||||||
*/
|
|
||||||
public function __construct(string $token, User $user) {
|
|
||||||
$this->token = $token;
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getToken(): string {
|
|
||||||
return $this->token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return User
|
|
||||||
*/
|
|
||||||
public function getUser(): User {
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,61 +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 CourtType {
|
|
||||||
private const COURT_PLAIN = 0;
|
|
||||||
private const COURT_HALF = 1;
|
|
||||||
private int $value;
|
|
||||||
|
|
||||||
private function __construct(int $val) {
|
|
||||||
if ($val < self::COURT_PLAIN || $val > self::COURT_HALF) {
|
|
||||||
throw new InvalidArgumentException("Valeur du rôle invalide");
|
|
||||||
}
|
|
||||||
$this->value = $val;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function plain(): CourtType {
|
|
||||||
return new CourtType(CourtType::COURT_PLAIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function half(): CourtType {
|
|
||||||
return new CourtType(CourtType::COURT_HALF);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function name(): string {
|
|
||||||
switch ($this->value) {
|
|
||||||
case self::COURT_HALF:
|
|
||||||
return "HALF";
|
|
||||||
case self::COURT_PLAIN:
|
|
||||||
return "PLAIN";
|
|
||||||
}
|
|
||||||
die("unreachable");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fromName(string $name): ?CourtType {
|
|
||||||
switch ($name) {
|
|
||||||
case "HALF":
|
|
||||||
return CourtType::half();
|
|
||||||
case "PLAIN":
|
|
||||||
return CourtType::plain();
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isPlain(): bool {
|
|
||||||
return ($this->value == self::COURT_PLAIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isHalf(): bool {
|
|
||||||
return ($this->value == self::COURT_HALF);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* information about a team member
|
|
||||||
*/
|
|
||||||
class Member implements \JsonSerializable {
|
|
||||||
private User $user;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int The member's team id
|
|
||||||
*/
|
|
||||||
private int $teamId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string the member's role
|
|
||||||
*/
|
|
||||||
private string $role;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param User $user
|
|
||||||
* @param int $teamId
|
|
||||||
* @param string $role
|
|
||||||
*/
|
|
||||||
public function __construct(User $user, int $teamId, string $role) {
|
|
||||||
$this->user = $user;
|
|
||||||
$this->teamId = $teamId;
|
|
||||||
$this->role = $role;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getRole(): string {
|
|
||||||
return $this->role;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
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,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Data;
|
|
||||||
|
|
||||||
class TacticInfo {
|
|
||||||
private int $id;
|
|
||||||
private string $name;
|
|
||||||
private int $creationDate;
|
|
||||||
private int $ownerId;
|
|
||||||
private CourtType $courtType;
|
|
||||||
private string $content;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $id
|
|
||||||
* @param string $name
|
|
||||||
* @param int $creationDate
|
|
||||||
* @param int $ownerId
|
|
||||||
* @param CourtType $type
|
|
||||||
* @param string $content
|
|
||||||
*/
|
|
||||||
public function __construct(int $id, string $name, int $creationDate, int $ownerId, CourtType $type, string $content) {
|
|
||||||
$this->id = $id;
|
|
||||||
$this->name = $name;
|
|
||||||
$this->ownerId = $ownerId;
|
|
||||||
$this->creationDate = $creationDate;
|
|
||||||
$this->courtType = $type;
|
|
||||||
$this->content = $content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getContent(): string {
|
|
||||||
return $this->content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): int {
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): string {
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getOwnerId(): int {
|
|
||||||
return $this->ownerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCourtType(): CourtType {
|
|
||||||
return $this->courtType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getCreationDate(): int {
|
|
||||||
return $this->creationDate;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Data;
|
|
||||||
|
|
||||||
class Team implements \JsonSerializable {
|
|
||||||
private TeamInfo $info;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Member[] maps users with their role
|
|
||||||
*/
|
|
||||||
private array $members;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TeamInfo $info
|
|
||||||
* @param Member[] $members
|
|
||||||
*/
|
|
||||||
public function __construct(TeamInfo $info, array $members = []) {
|
|
||||||
$this->info = $info;
|
|
||||||
$this->members = $members;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getInfo(): TeamInfo {
|
|
||||||
return $this->info;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Member[]
|
|
||||||
*/
|
|
||||||
public function listMembers(): array {
|
|
||||||
return $this->members;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function jsonSerialize() {
|
|
||||||
return get_object_vars($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Data;
|
|
||||||
|
|
||||||
class TeamInfo implements \JsonSerializable {
|
|
||||||
private int $id;
|
|
||||||
private string $name;
|
|
||||||
private string $picture;
|
|
||||||
private string $mainColor;
|
|
||||||
private string $secondColor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $id
|
|
||||||
* @param string $name
|
|
||||||
* @param string $picture
|
|
||||||
* @param string $mainColor
|
|
||||||
* @param string $secondColor
|
|
||||||
*/
|
|
||||||
public function __construct(int $id, string $name, string $picture, string $mainColor, string $secondColor) {
|
|
||||||
$this->id = $id;
|
|
||||||
$this->name = $name;
|
|
||||||
$this->picture = $picture;
|
|
||||||
$this->mainColor = $mainColor;
|
|
||||||
$this->secondColor = $secondColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): int {
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): string {
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPicture(): string {
|
|
||||||
return $this->picture;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMainColor(): string {
|
|
||||||
return $this->mainColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSecondColor(): string {
|
|
||||||
return $this->secondColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function jsonSerialize() {
|
|
||||||
return get_object_vars($this);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Data;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool true if the user is an administrator
|
|
||||||
*/
|
|
||||||
private bool $isAdmin;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $email
|
|
||||||
* @param string $name
|
|
||||||
* @param int $id
|
|
||||||
* @param string $profilePicture
|
|
||||||
* @param bool $isAdmin
|
|
||||||
*/
|
|
||||||
public function __construct(string $email, string $name, int $id, string $profilePicture, bool $isAdmin) {
|
|
||||||
$this->email = $email;
|
|
||||||
$this->name = $name;
|
|
||||||
$this->id = $id;
|
|
||||||
$this->profilePicture = $profilePicture;
|
|
||||||
$this->isAdmin = $isAdmin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isAdmin(): bool {
|
|
||||||
return $this->isAdmin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,180 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Gateway;
|
|
||||||
|
|
||||||
use IQBall\Core\Connection;
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
use IQBall\Core\Data\User;
|
|
||||||
use PDO;
|
|
||||||
|
|
||||||
class AccountGateway {
|
|
||||||
private Connection $con;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Connection $con
|
|
||||||
*/
|
|
||||||
public function __construct(Connection $con) {
|
|
||||||
$this->con = $con;
|
|
||||||
}
|
|
||||||
|
|
||||||
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],
|
|
||||||
':hash' => [$hash, PDO::PARAM_STR],
|
|
||||||
':email' => [$email, PDO::PARAM_STR],
|
|
||||||
':token' => [$token, PDO::PARAM_STR],
|
|
||||||
':profile_pic' => [$profilePicture, PDO::PARAM_STR],
|
|
||||||
]);
|
|
||||||
return intval($this->con->lastInsertId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateAccount(int $id, string $name, string $email, string $token, bool $isAdmin): void {
|
|
||||||
$this->con->exec("UPDATE Account SET username = :username, email = :email, token = :token WHERE id = :id", [
|
|
||||||
':username' => [$name, PDO::PARAM_STR],
|
|
||||||
':email' => [$email, PDO::PARAM_STR],
|
|
||||||
':token' => [$token, PDO::PARAM_STR],
|
|
||||||
':id' => [$id, PDO::PARAM_INT],
|
|
||||||
]);
|
|
||||||
$this->setIsAdmin($id, $isAdmin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAdmin(int $id): bool {
|
|
||||||
$stmnt = $this->con->prepare("SELECT * FROM Admins WHERE id = :id");
|
|
||||||
$stmnt->bindValue(':id', $id, PDO::PARAM_INT);
|
|
||||||
$stmnt->execute();
|
|
||||||
$result = $stmnt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
return !empty($result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* promote or demote a user to server administrator
|
|
||||||
* @param int $id
|
|
||||||
* @param bool $isAdmin true to promote, false to demote
|
|
||||||
* @return bool true if the given user exists
|
|
||||||
*/
|
|
||||||
public function setIsAdmin(int $id, bool $isAdmin): bool {
|
|
||||||
if ($isAdmin) {
|
|
||||||
$stmnt = $this->con->prepare("INSERT INTO Admins VALUES(:id)");
|
|
||||||
} else {
|
|
||||||
$stmnt = $this->con->prepare("DELETE FROM Admins WHERE id = :id");
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmnt->bindValue(':id', $id);
|
|
||||||
$stmnt->execute();
|
|
||||||
|
|
||||||
return $stmnt->rowCount() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $email
|
|
||||||
* @return array<string, mixed>|null
|
|
||||||
*/
|
|
||||||
private function getRowsFromMail(string $email): ?array {
|
|
||||||
return $this->con->fetch("SELECT * FROM Account WHERE email = :email", [':email' => [$email, PDO::PARAM_STR]])[0] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $email
|
|
||||||
* @return string|null the hashed user's password, or null if the given mail does not exist
|
|
||||||
*/
|
|
||||||
public function getHash(string $email): ?string {
|
|
||||||
$results = $this->getRowsFromMail($email);
|
|
||||||
if ($results == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return $results['hash'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $email
|
|
||||||
* @return bool true if the given email exists in the database
|
|
||||||
*/
|
|
||||||
public function exists(string $email): bool {
|
|
||||||
return $this->getRowsFromMail($email) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $email
|
|
||||||
* @return Account|null
|
|
||||||
*/
|
|
||||||
public function getAccountFromMail(string $email): ?Account {
|
|
||||||
$acc = $this->getRowsFromMail($email);
|
|
||||||
if (empty($acc)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Account($acc["token"], new User($email, $acc["username"], $acc["id"], $acc["profile_picture"], $this->isAdmin($acc["id"])));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $token get an account from given token
|
|
||||||
* @return Account|null
|
|
||||||
*/
|
|
||||||
public function getAccountFromToken(string $token): ?Account {
|
|
||||||
$stmnt = $this->con->prepare("SELECT * FROM Account WHERE token = :token");
|
|
||||||
$stmnt->bindValue(':token', $token);
|
|
||||||
return $this->getAccountFrom($stmnt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $id get an account from given identifier
|
|
||||||
* @return Account|null
|
|
||||||
*/
|
|
||||||
public function getAccount(int $id): ?Account {
|
|
||||||
$stmnt = $this->con->prepare("SELECT * FROM Account WHERE id = :id");
|
|
||||||
$stmnt->bindValue(':id', $id);
|
|
||||||
return $this->getAccountFrom($stmnt);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getAccountFrom(\PDOStatement $stmnt): ?Account {
|
|
||||||
$stmnt->execute();
|
|
||||||
$acc = $stmnt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($acc == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Account($acc["token"], new User($acc["email"], $acc["username"], $acc["id"], $acc["profile_picture"], $this->isAdmin($acc["id"])));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list containing n accounts from a given starting index
|
|
||||||
*
|
|
||||||
* @param integer $n the number of accounts to retrieve
|
|
||||||
* @param int $start starting index of the list content
|
|
||||||
* @return Account[]
|
|
||||||
*/
|
|
||||||
public function searchAccounts(int $start, int $n, ?string $searchString): array {
|
|
||||||
$res = $this->con->fetch(
|
|
||||||
"SELECT * FROM Account WHERE username LIKE '%' || :search || '%' OR email LIKE '%' || :search || '%' ORDER BY username, email LIMIT :offset, :n",
|
|
||||||
[
|
|
||||||
":offset" => [$start, PDO::PARAM_INT],
|
|
||||||
":n" => [$n, PDO::PARAM_INT],
|
|
||||||
":search" => [$searchString ?? "", PDO::PARAM_STR],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
return array_map(fn(array $acc) => new Account($acc["token"], new User($acc["email"], $acc["username"], $acc["id"], $acc["profile_picture"], $this->isAdmin($acc["id"]))), $res);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the total amount of accounts in the database
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function totalCount(): int {
|
|
||||||
return $this->con->fetch("SELECT count(*) FROM Account", [])[0]['count(*)'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove a bunch of account identifiers
|
|
||||||
* @param int[] $accountIds
|
|
||||||
*/
|
|
||||||
public function removeAccounts(array $accountIds): void {
|
|
||||||
foreach ($accountIds as $accountId) {
|
|
||||||
$this->con->fetch("DELETE FROM Account WHERE id = :accountId", [
|
|
||||||
":accountId" => [$accountId, PDO::PARAM_INT],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Gateway;
|
|
||||||
|
|
||||||
use IQBall\Core\Connection;
|
|
||||||
use IQBall\Core\Data\Member;
|
|
||||||
use IQBall\Core\Data\User;
|
|
||||||
use PDO;
|
|
||||||
|
|
||||||
class MemberGateway {
|
|
||||||
private Connection $con;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Connection $con
|
|
||||||
*/
|
|
||||||
public function __construct(Connection $con) {
|
|
||||||
$this->con = $con;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* insert member to a team
|
|
||||||
* @param int $idTeam
|
|
||||||
* @param int $userId
|
|
||||||
* @param string $role
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function insert(int $idTeam, int $userId, string $role): void {
|
|
||||||
$this->con->exec(
|
|
||||||
"INSERT INTO Member(id_team, id_user, role) VALUES (:id_team, :id_user, :role)",
|
|
||||||
[
|
|
||||||
":id_team" => [$idTeam, PDO::PARAM_INT],
|
|
||||||
":id_user" => [$userId, PDO::PARAM_INT],
|
|
||||||
":role" => [$role, PDO::PARAM_STR],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $teamId
|
|
||||||
* @return Member[]
|
|
||||||
*/
|
|
||||||
public function getMembersOfTeam(int $teamId): array {
|
|
||||||
$rows = $this->con->fetch(
|
|
||||||
"SELECT a.id,a.email,a.username,a.profile_picture,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(new User($row['email'], $row['username'], $row['id'], $row['profile_picture'], $row['is_admin']), $teamId, $row['role']), $rows);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove member from given team
|
|
||||||
* @param int $idTeam
|
|
||||||
* @param int $idMember
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function remove(int $idTeam, int $idMember): void {
|
|
||||||
$this->con->exec(
|
|
||||||
"DELETE FROM Member WHERE id_team = :id_team AND id_user = :id_user",
|
|
||||||
[
|
|
||||||
":id_team" => [$idTeam, PDO::PARAM_INT],
|
|
||||||
":id_user" => [$idMember, PDO::PARAM_INT],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Gateway;
|
|
||||||
|
|
||||||
use IQBall\Core\Connection;
|
|
||||||
use IQBall\Core\Data\CourtType;
|
|
||||||
use IQBall\Core\Data\TacticInfo;
|
|
||||||
use PDO;
|
|
||||||
|
|
||||||
class TacticInfoGateway {
|
|
||||||
private Connection $con;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Connection $con
|
|
||||||
*/
|
|
||||||
public function __construct(Connection $con) {
|
|
||||||
$this->con = $con;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get tactic information from given identifier
|
|
||||||
* @param int $id
|
|
||||||
* @return TacticInfo|null
|
|
||||||
*/
|
|
||||||
public function get(int $id): ?TacticInfo {
|
|
||||||
$res = $this->con->fetch(
|
|
||||||
"SELECT * FROM Tactic WHERE id = :id",
|
|
||||||
[":id" => [$id, PDO::PARAM_INT]]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isset($res[0])) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$row = $res[0];
|
|
||||||
|
|
||||||
$type = CourtType::fromName($row['court_type']);
|
|
||||||
return new TacticInfo($id, $row["name"], strtotime($row["creation_date"]), $row["owner"], $type, $row['content']);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the nb last tactics created
|
|
||||||
*
|
|
||||||
* @param integer $nb
|
|
||||||
* @return array<array<string,mixed>>
|
|
||||||
*/
|
|
||||||
public function getLast(int $nb, int $ownerId): ?array {
|
|
||||||
$res = $this->con->fetch(
|
|
||||||
"SELECT *
|
|
||||||
FROM Tactic
|
|
||||||
WHERE owner = :ownerId
|
|
||||||
ORDER BY creation_date DESC
|
|
||||||
LIMIT :nb",
|
|
||||||
[
|
|
||||||
":ownerId" => [$ownerId, PDO::PARAM_INT],":nb" => [$nb, PDO::PARAM_INT],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
if (count($res) == 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all the tactics of the owner
|
|
||||||
*
|
|
||||||
* @return array<array<string,mixed>>
|
|
||||||
*/
|
|
||||||
public function getAll(int $ownerId): ?array {
|
|
||||||
$res = $this->con->fetch(
|
|
||||||
"SELECT *
|
|
||||||
FROM Tactic
|
|
||||||
WHERE owner = :ownerId
|
|
||||||
ORDER BY name DESC",
|
|
||||||
[
|
|
||||||
":ownerId" => [$ownerId, PDO::PARAM_INT],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
if (count($res) == 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return $res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list containing the nth last tactics of a given user id
|
|
||||||
*
|
|
||||||
* @param integer $user_id
|
|
||||||
* @return TacticInfo[]
|
|
||||||
*/
|
|
||||||
public function listAllOf(int $user_id): array {
|
|
||||||
$res = $this->con->fetch(
|
|
||||||
"SELECT * FROM Tactic WHERE owner = :owner_id ORDER BY creation_date DESC",
|
|
||||||
[
|
|
||||||
":owner_id" => [$user_id, PDO::PARAM_STR],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
return array_map(fn(array $t) => new TacticInfo($t['id'], $t["name"], strtotime($t["creation_date"]), $t["owner"], CourtType::fromName($t['court_type']), $t['content']), $res);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $name
|
|
||||||
* @param int $owner
|
|
||||||
* @param CourtType $type
|
|
||||||
* @return int inserted tactic id
|
|
||||||
*/
|
|
||||||
public function insert(string $name, int $owner, CourtType $type): int {
|
|
||||||
$this->con->exec(
|
|
||||||
"INSERT INTO Tactic(name, owner, court_type) VALUES(:name, :owner, :court_type)",
|
|
||||||
[
|
|
||||||
":name" => [$name, PDO::PARAM_STR],
|
|
||||||
":owner" => [$owner, PDO::PARAM_INT],
|
|
||||||
":court_type" => [$type->name(), PDO::PARAM_STR],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
return intval($this->con->lastInsertId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* update name of given tactic identifier
|
|
||||||
* @param int $id
|
|
||||||
* @param string $name
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function updateName(int $id, string $name): bool {
|
|
||||||
$stmnt = $this->con->prepare("UPDATE Tactic SET name = :name WHERE id = :id");
|
|
||||||
$stmnt->execute([
|
|
||||||
":name" => $name,
|
|
||||||
":id" => $id,
|
|
||||||
]);
|
|
||||||
return $stmnt->rowCount() == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Updates a given tactics content
|
|
||||||
* @param int $id
|
|
||||||
* @param string $json
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function updateContent(int $id, string $json): bool {
|
|
||||||
$stmnt = $this->con->prepare("UPDATE Tactic SET content = :content WHERE id = :id");
|
|
||||||
$stmnt->execute([
|
|
||||||
":content" => $json,
|
|
||||||
":id" => $id,
|
|
||||||
]);
|
|
||||||
return $stmnt->rowCount() == 1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,186 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Gateway;
|
|
||||||
|
|
||||||
use IQBall\Core\Connection;
|
|
||||||
use IQBall\Core\Data\Color;
|
|
||||||
use IQBall\Core\Data\Team;
|
|
||||||
use IQBall\Core\Data\TeamInfo;
|
|
||||||
use PDO;
|
|
||||||
|
|
||||||
class TeamGateway {
|
|
||||||
private Connection $con;
|
|
||||||
|
|
||||||
public function __construct(Connection $con) {
|
|
||||||
$this->con = $con;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $name
|
|
||||||
* @param string $picture
|
|
||||||
* @param string $mainColor
|
|
||||||
* @param string $secondColor
|
|
||||||
* @return int the inserted team identifier
|
|
||||||
*/
|
|
||||||
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)",
|
|
||||||
[
|
|
||||||
":team_name" => [$name, PDO::PARAM_STR],
|
|
||||||
":picture" => [$picture, PDO::PARAM_STR],
|
|
||||||
":main_color" => [$mainColor, PDO::PARAM_STR],
|
|
||||||
":second_color" => [$secondColor, PDO::PARAM_STR],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
return intval($this->con->lastInsertId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $name
|
|
||||||
* @param int $id
|
|
||||||
* @return TeamInfo[]
|
|
||||||
*/
|
|
||||||
public function listByName(string $name, int $id): array {
|
|
||||||
$result = $this->con->fetch(
|
|
||||||
"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'], $row['main_color'], $row['second_color']), $result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $id
|
|
||||||
* @return TeamInfo|null
|
|
||||||
*/
|
|
||||||
public function getTeamById(int $id): ?TeamInfo {
|
|
||||||
$row = $this->con->fetch(
|
|
||||||
"SELECT * FROM team WHERE id = :id",
|
|
||||||
[
|
|
||||||
":id" => [$id, PDO::PARAM_INT],
|
|
||||||
]
|
|
||||||
)[0] ?? null;
|
|
||||||
if ($row == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new TeamInfo($row['id'], $row['name'], $row['picture'], $row['main_color'], $row['second_color']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $name
|
|
||||||
* @return int|null
|
|
||||||
*/
|
|
||||||
public function getTeamIdByName(string $name): ?int {
|
|
||||||
return $this->con->fetch(
|
|
||||||
"SELECT id FROM team WHERE name = :name",
|
|
||||||
[
|
|
||||||
":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],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $user
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function getAll(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 ",
|
|
||||||
[
|
|
||||||
"idUser" => [$user, PDO::PARAM_INT],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $start
|
|
||||||
* @param int $n
|
|
||||||
* @return TeamInfo[]
|
|
||||||
*/
|
|
||||||
public function listAll(int $start, int $n): array {
|
|
||||||
$rows = $this->con->fetch(
|
|
||||||
"SELECT * FROM Team LIMIT :start, :n",
|
|
||||||
[
|
|
||||||
":start" => [$start, PDO::PARAM_INT],
|
|
||||||
":n" => [$n, PDO::PARAM_INT],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
return array_map(fn($row) => new TeamInfo($row['id'], $row['name'], $row['picture'], $row['main_color'], $row['second_color']), $rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function countTeam(): int {
|
|
||||||
$result = $this->con->fetch(
|
|
||||||
"SELECT count(*) as count FROM Team",
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
if (empty($result) || !isset($result[0]['count'])) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return $result[0]['count'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<Team> $selectedTeams
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function deleteTeamSelected(array $selectedTeams): void {
|
|
||||||
foreach ($selectedTeams as $team) {
|
|
||||||
$this->con->exec(
|
|
||||||
"DELETE FROM TEAM WHERE id=:team",
|
|
||||||
[
|
|
||||||
"team" => [$team, PDO::PARAM_INT],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Http;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class to define constants of used http codes
|
|
||||||
*/
|
|
||||||
class HttpCodes {
|
|
||||||
public const OK = 200;
|
|
||||||
public const FOUND = 302;
|
|
||||||
public const BAD_REQUEST = 400;
|
|
||||||
public const UNAUTHORIZED = 401;
|
|
||||||
|
|
||||||
public const FORBIDDEN = 403;
|
|
||||||
|
|
||||||
public const NOT_FOUND = 404;
|
|
||||||
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Http;
|
|
||||||
|
|
||||||
use IQBall\Core\Validation\FieldValidationFail;
|
|
||||||
use IQBall\Core\Validation\Validation;
|
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
|
||||||
use IQBall\Core\Validation\Validator;
|
|
||||||
use ArrayAccess;
|
|
||||||
use Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @implements ArrayAccess<string, mixed>
|
|
||||||
* */
|
|
||||||
class HttpRequest implements ArrayAccess {
|
|
||||||
/**
|
|
||||||
* @var array<string, mixed>
|
|
||||||
*/
|
|
||||||
private array $data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $data
|
|
||||||
*/
|
|
||||||
private function __construct(array $data) {
|
|
||||||
$this->data = $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new HttpRequest instance, and ensures that the given request data validates the given schema.
|
|
||||||
* This is a simple function that only supports flat schemas (non-composed, the data must only be a k/v array pair.)
|
|
||||||
* @param array<string, mixed> $request the request's data
|
|
||||||
* @param array<string, ValidationFail> $fails a reference to a failure array, that will contain the reported validation failures.
|
|
||||||
* @param array<string, Validator[]> $schema the schema to satisfy. a schema is a simple array with a string key (which is the top-level field name), and a set of validators
|
|
||||||
* @return HttpRequest|null the built HttpRequest instance, or null if a field is missing, or if any of the schema validator failed
|
|
||||||
*/
|
|
||||||
public static function from(array $request, array &$fails, array $schema): ?HttpRequest {
|
|
||||||
$failure = false;
|
|
||||||
foreach ($schema as $fieldName => $fieldValidators) {
|
|
||||||
if (!isset($request[$fieldName])) {
|
|
||||||
$fails[] = FieldValidationFail::missing($fieldName);
|
|
||||||
$failure = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$failure |= Validation::validate($request[$fieldName], $fieldName, $fails, ...$fieldValidators);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($failure) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new HttpRequest($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function offsetExists($offset): bool {
|
|
||||||
return isset($this->data[$offset]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $offset
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function offsetGet($offset) {
|
|
||||||
return $this->data[$offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $offset
|
|
||||||
* @param $value
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function offsetSet($offset, $value) {
|
|
||||||
throw new Exception("requests are immutable objects.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $offset
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function offsetUnset($offset) {
|
|
||||||
throw new Exception("requests are immutable objects.");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Http;
|
|
||||||
|
|
||||||
class HttpResponse {
|
|
||||||
/**
|
|
||||||
* @var array<string, string>
|
|
||||||
*/
|
|
||||||
private array $headers;
|
|
||||||
private int $code;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $code
|
|
||||||
* @param array<string, string> $headers
|
|
||||||
*/
|
|
||||||
public function __construct(int $code, array $headers) {
|
|
||||||
$this->code = $code;
|
|
||||||
$this->headers = $headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCode(): int {
|
|
||||||
return $this->code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
public function getHeaders(): array {
|
|
||||||
return $this->headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $code
|
|
||||||
* @return HttpResponse
|
|
||||||
*/
|
|
||||||
public static function fromCode(int $code): HttpResponse {
|
|
||||||
return new HttpResponse($code, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $url the url to redirect
|
|
||||||
* @param int $code only HTTP 3XX codes are accepted.
|
|
||||||
* @return HttpResponse a response that will redirect client to given url
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static function redirect(string $url, int $code = HttpCodes::FOUND): HttpResponse {
|
|
||||||
global $basePath;
|
|
||||||
return self::redirectAbsolute($basePath . $url, $code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $url the url to redirect
|
|
||||||
* @param int $code only HTTP 3XX codes are accepted.
|
|
||||||
* @return HttpResponse a response that will redirect client to given url
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static function redirectAbsolute(string $url, int $code = HttpCodes::FOUND): HttpResponse {
|
|
||||||
if ($code < 300 || $code >= 400) {
|
|
||||||
throw new \InvalidArgumentException("given code is not a redirection http code");
|
|
||||||
}
|
|
||||||
return new HttpResponse($code, ["Location" => $url]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Http;
|
|
||||||
|
|
||||||
class JsonHttpResponse extends HttpResponse {
|
|
||||||
/**
|
|
||||||
* @var mixed Any JSON serializable value
|
|
||||||
*/
|
|
||||||
private $payload;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $payload
|
|
||||||
*/
|
|
||||||
public function __construct($payload, int $code = HttpCodes::OK) {
|
|
||||||
parent::__construct($code, []);
|
|
||||||
$this->payload = $payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getJson(): string {
|
|
||||||
$result = json_encode($this->payload);
|
|
||||||
if (!$result) {
|
|
||||||
throw new \RuntimeException("Given payload is not json encodable");
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Model;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use IQBall\Core\Data\Account;
|
|
||||||
use IQBall\Core\Data\User;
|
|
||||||
use IQBall\Core\Gateway\AccountGateway;
|
|
||||||
use IQBall\Core\Http\HttpCodes;
|
|
||||||
use IQBall\Core\Http\JsonHttpResponse;
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
public function __construct(AccountGateway $gateway) {
|
|
||||||
$this->gateway = $gateway;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $username
|
|
||||||
* @param string $password
|
|
||||||
* @param string $email
|
|
||||||
* @return Account|null the registered account or null if the account already exists for the given email address
|
|
||||||
*/
|
|
||||||
public function register(
|
|
||||||
string $username,
|
|
||||||
string $password,
|
|
||||||
string $email
|
|
||||||
): ?Account {
|
|
||||||
if ($this->gateway->exists($email)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
|
||||||
$token = $this->generateToken();
|
|
||||||
$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, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random base 64 string
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function generateToken(): string {
|
|
||||||
try {
|
|
||||||
return base64_encode(random_bytes(64));
|
|
||||||
} catch (Exception $e) {
|
|
||||||
throw new \RuntimeException($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $email
|
|
||||||
* @param string $password
|
|
||||||
* @param ValidationFail[] $failures
|
|
||||||
* @return Account|null the authenticated account or null if failures occurred
|
|
||||||
*/
|
|
||||||
public function login(string $email, string $password, array &$failures): ?Account {
|
|
||||||
$hash = $this->gateway->getHash($email);
|
|
||||||
if ($hash == null or (!password_verify($password, $hash))) {
|
|
||||||
$failures[] = new ValidationFail("email", "Adresse email ou mot de passe invalide");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return $this->gateway->getAccountFromMail($email);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(int $id, string $email, string $username, bool $isAdmin): void {
|
|
||||||
$token = $this->generateToken();
|
|
||||||
$this->gateway->updateAccount($id, $username, $email, $token, $isAdmin);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Model;
|
|
||||||
|
|
||||||
use IQBall\Core\Data\CourtType;
|
|
||||||
use IQBall\App\Session\SessionHandle;
|
|
||||||
use IQBall\Core\Data\TacticInfo;
|
|
||||||
use IQBall\Core\Gateway\TacticInfoGateway;
|
|
||||||
use IQBall\Core\Validation\ValidationFail;
|
|
||||||
|
|
||||||
class TacticModel {
|
|
||||||
public const TACTIC_DEFAULT_NAME = "Nouvelle tactique";
|
|
||||||
|
|
||||||
private TacticInfoGateway $tactics;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TacticInfoGateway $tactics
|
|
||||||
*/
|
|
||||||
public function __construct(TacticInfoGateway $tactics) {
|
|
||||||
$this->tactics = $tactics;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a new empty tactic, with given name
|
|
||||||
* @param string $name
|
|
||||||
* @param int $ownerId
|
|
||||||
* @param CourtType $type
|
|
||||||
* @return TacticInfo
|
|
||||||
*/
|
|
||||||
public function makeNew(string $name, int $ownerId, CourtType $type): TacticInfo {
|
|
||||||
$id = $this->tactics->insert($name, $ownerId, $type);
|
|
||||||
return $this->tactics->get($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a new empty tactic, with a default name
|
|
||||||
* @param int $ownerId
|
|
||||||
* @param CourtType $type
|
|
||||||
* @return TacticInfo|null
|
|
||||||
*/
|
|
||||||
public function makeNewDefault(int $ownerId, CourtType $type): ?TacticInfo {
|
|
||||||
return $this->makeNew(self::TACTIC_DEFAULT_NAME, $ownerId, $type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to retrieve information about a tactic
|
|
||||||
* @param int $id tactic identifier
|
|
||||||
* @return TacticInfo|null or null if the identifier did not match a tactic
|
|
||||||
*/
|
|
||||||
public function get(int $id): ?TacticInfo {
|
|
||||||
return $this->tactics->get($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the nb last tactics
|
|
||||||
*
|
|
||||||
* @param integer $nb
|
|
||||||
* @param integer $ownerId
|
|
||||||
* @return array<array<string,mixed>>
|
|
||||||
*/
|
|
||||||
public function getLast(int $nb, int $ownerId): array {
|
|
||||||
return $this->tactics->getLast($nb, $ownerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list containing all the tactics of a given user
|
|
||||||
* NOTE: if given user id does not match any user, this function returns an empty array
|
|
||||||
*
|
|
||||||
* @param integer $user_id
|
|
||||||
* @return TacticInfo[]
|
|
||||||
*/
|
|
||||||
public function listAllOf(int $user_id): array {
|
|
||||||
return$this->tactics->listAllOf($user_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all the tactics of the owner
|
|
||||||
*
|
|
||||||
* @param integer $ownerId
|
|
||||||
* @return array<array<string,mixed>>
|
|
||||||
*/
|
|
||||||
public function getAll(int $ownerId): ?array {
|
|
||||||
return $this->tactics->getAll($ownerId);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Update the name of a tactic
|
|
||||||
* @param int $id the tactic identifier
|
|
||||||
* @param string $name the new name to set
|
|
||||||
* @return ValidationFail[] failures, if any
|
|
||||||
*/
|
|
||||||
public function updateName(int $id, string $name, int $authId): array {
|
|
||||||
|
|
||||||
$tactic = $this->tactics->get($id);
|
|
||||||
|
|
||||||
if ($tactic == null) {
|
|
||||||
return [ValidationFail::notFound("Could not find tactic")];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($tactic->getOwnerId() != $authId) {
|
|
||||||
return [ValidationFail::unauthorized()];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->tactics->updateName($id, $name)) {
|
|
||||||
return [ValidationFail::error("Could not update name")];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateContent(int $id, string $json): ?ValidationFail {
|
|
||||||
if (!$this->tactics->updateContent($id, $json)) {
|
|
||||||
return ValidationFail::error("Could not update content");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,164 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Model;
|
|
||||||
|
|
||||||
use IQBall\Core\Data\Team;
|
|
||||||
use IQBall\Core\Data\TeamInfo;
|
|
||||||
use IQBall\Core\Gateway\AccountGateway;
|
|
||||||
use IQBall\Core\Gateway\MemberGateway;
|
|
||||||
use IQBall\Core\Gateway\TeamGateway;
|
|
||||||
|
|
||||||
class TeamModel {
|
|
||||||
private AccountGateway $users;
|
|
||||||
private TeamGateway $teams;
|
|
||||||
private MemberGateway $members;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param TeamGateway $gateway
|
|
||||||
* @param MemberGateway $members
|
|
||||||
* @param AccountGateway $users
|
|
||||||
*/
|
|
||||||
public function __construct(TeamGateway $gateway, MemberGateway $members, AccountGateway $users) {
|
|
||||||
$this->teams = $gateway;
|
|
||||||
$this->members = $members;
|
|
||||||
$this->users = $users;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a team
|
|
||||||
* @param string $name
|
|
||||||
* @param string $picture
|
|
||||||
* @param string $mainColor
|
|
||||||
* @param string $secondColor
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function createTeam(string $name, string $picture, string $mainColor, string $secondColor): int {
|
|
||||||
return $this->teams->insert($name, $picture, $mainColor, $secondColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add a member to a team
|
|
||||||
* @param string $mail
|
|
||||||
* @param int $teamId
|
|
||||||
* @param string $role
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
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, int $id): array {
|
|
||||||
return $this->teams->listByName($name, $id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $idTeam
|
|
||||||
* @param int $idCurrentUser
|
|
||||||
* @return Team|null
|
|
||||||
*/
|
|
||||||
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 int $idMember
|
|
||||||
* @param int $teamId
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
*
|
|
||||||
* @param integer $user
|
|
||||||
* @return array<array<string, mixed>>
|
|
||||||
*/
|
|
||||||
public function getAll(int $user): array {
|
|
||||||
return $this->teams->getAll($user);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $start
|
|
||||||
* @param int $n
|
|
||||||
* @return TeamInfo[]
|
|
||||||
*/
|
|
||||||
public function listAll(int $start, int $n) {
|
|
||||||
return $this->teams->listAll($start, $n);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function countTeam(): int {
|
|
||||||
return $this->teams->countTeam();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<Team> $selectedTeams
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function deleteTeamSelected(array $selectedTeams) {
|
|
||||||
$this->teams->deleteTeamSelected($selectedTeams);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Validation;
|
|
||||||
|
|
||||||
class ComposedValidator extends Validator {
|
|
||||||
private Validator $first;
|
|
||||||
private Validator $then;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Validator $first
|
|
||||||
* @param Validator $then
|
|
||||||
*/
|
|
||||||
public function __construct(Validator $first, Validator $then) {
|
|
||||||
$this->first = $first;
|
|
||||||
$this->then = $then;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validate(string $name, $val): array {
|
|
||||||
$firstFailures = $this->first->validate($name, $val);
|
|
||||||
$thenFailures = [];
|
|
||||||
if (empty($firstFailures)) {
|
|
||||||
$thenFailures = $this->then->validate($name, $val);
|
|
||||||
}
|
|
||||||
return array_merge($firstFailures, $thenFailures);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Validation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of standard validators
|
|
||||||
*/
|
|
||||||
class DefaultValidators {
|
|
||||||
/**
|
|
||||||
* @return Validator a validator that validates a given regex
|
|
||||||
*/
|
|
||||||
public static function regex(string $regex, ?string $msg = null): Validator {
|
|
||||||
return new SimpleFunctionValidator(
|
|
||||||
fn(string $str) => preg_match($regex, $str),
|
|
||||||
fn(string $name) => [new FieldValidationFail($name, $msg == null ? "le champ ne valide pas le pattern $regex" : $msg)]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function hex(?string $msg = null): Validator {
|
|
||||||
return self::regex('/#([0-9a-fA-F])/', $msg == null ? "le champ n'est pas un nombre hexadecimal valide" : $msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function hexColor(?string $msg = null): Validator {
|
|
||||||
return self::regex('/#([0-9a-fA-F]{6})/', $msg == null ? "le champ n'est pas une couleur valide" : $msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Validator a validator that validates strings that only contains numbers, letters, accents letters, `-` and `_`.
|
|
||||||
*/
|
|
||||||
public static function name(?string $msg = null): Validator {
|
|
||||||
return self::regex("/^[0-9a-zA-Zà-üÀ-Ü_-]*$/", $msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Validator a validator that validates strings that only contains numbers, letters, accents letters, `-`, `_` and spaces.
|
|
||||||
*/
|
|
||||||
public static function nameWithSpaces(): Validator {
|
|
||||||
return self::regex("/^[0-9a-zA-Zà-üÀ-Ü _-]*$/");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function password(): Validator {
|
|
||||||
return self::lenBetween(6, 256);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate string if its length is between given range
|
|
||||||
* @param int $min minimum accepted length, inclusive
|
|
||||||
* @param int $max maximum accepted length, exclusive
|
|
||||||
* @return Validator
|
|
||||||
*/
|
|
||||||
public static function lenBetween(int $min, int $max): Validator {
|
|
||||||
return new FunctionValidator(
|
|
||||||
function (string $fieldName, string $str) use ($min, $max) {
|
|
||||||
$len = strlen($str);
|
|
||||||
if ($len >= $max) {
|
|
||||||
return [new FieldValidationFail($fieldName, "trop long, maximum $max caractères.")];
|
|
||||||
}
|
|
||||||
if ($len < $min) {
|
|
||||||
return [new FieldValidationFail($fieldName, "trop court, minimum $min caractères.")];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function email(?string $msg = null): Validator {
|
|
||||||
return new SimpleFunctionValidator(
|
|
||||||
fn(string $str) => filter_var($str, FILTER_VALIDATE_EMAIL),
|
|
||||||
fn(string $name) => [new FieldValidationFail($name, $msg == null ? "addresse mail invalide" : $msg)]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static function isInteger(): Validator {
|
|
||||||
return self::regex("/^[-+]?[0-9]+$/", "field is not an integer");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isUnsignedInteger(): Validator {
|
|
||||||
return self::regex("/^[0-9]+$/", "field is not an unsigned integer");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isIntInRange(int $min, int $max): Validator {
|
|
||||||
return new SimpleFunctionValidator(
|
|
||||||
fn(string $val) => intval($val) >= $min && intval($val) <= $max,
|
|
||||||
fn(string $name) => [new FieldValidationFail($name, "The value is not in the range $min to $max ")]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed[] $values
|
|
||||||
* @return Validator
|
|
||||||
*/
|
|
||||||
public static function oneOf(array $values): Validator {
|
|
||||||
return new SimpleFunctionValidator(
|
|
||||||
fn(string $val) => in_array($val, $values),
|
|
||||||
fn(string $name) => [new FieldValidationFail($name, "The value must be one of '" . join(", ", $values) . "'")]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function bool(): Validator {
|
|
||||||
return self::oneOf([true, false]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isURL(): Validator {
|
|
||||||
return new SimpleFunctionValidator(
|
|
||||||
fn($val) => filter_var($val, FILTER_VALIDATE_URL),
|
|
||||||
fn(string $name) => [new FieldValidationFail($name, "The value is not an URL")]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Validator
|
|
||||||
*/
|
|
||||||
public static function array(): Validator {
|
|
||||||
return new SimpleFunctionValidator(
|
|
||||||
fn($val) => is_array($val),
|
|
||||||
fn(string $name) => [new FieldValidationFail($name, "The value is not an array")]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Validator $validator
|
|
||||||
* @return Validator
|
|
||||||
*/
|
|
||||||
public static function forall(Validator $validator): Validator {
|
|
||||||
return new class ($validator) extends Validator {
|
|
||||||
private Validator $validator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Validator $validator
|
|
||||||
*/
|
|
||||||
public function __construct(Validator $validator) {
|
|
||||||
$this->validator = $validator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validate(string $name, $val): array {
|
|
||||||
$failures = [];
|
|
||||||
foreach ($val as $idx => $item) {
|
|
||||||
$failures = array_merge($failures, $this->validator->validate($name . "[$idx]", $item));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $failures;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Validation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An error that concerns a field, with a bound message name
|
|
||||||
*/
|
|
||||||
class FieldValidationFail extends ValidationFail {
|
|
||||||
private string $fieldName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $fieldName
|
|
||||||
* @param string $message
|
|
||||||
*/
|
|
||||||
public function __construct(string $fieldName, string $message) {
|
|
||||||
parent::__construct("Champ invalide", $message);
|
|
||||||
$this->fieldName = $fieldName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFieldName(): string {
|
|
||||||
return $this->fieldName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function invalidChars(string $fieldName): FieldValidationFail {
|
|
||||||
return new FieldValidationFail($fieldName, "field contains illegal chars");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function empty(string $fieldName): FieldValidationFail {
|
|
||||||
return new FieldValidationFail($fieldName, "field is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function missing(string $fieldName): FieldValidationFail {
|
|
||||||
return new FieldValidationFail($fieldName, "field is missing");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
public function jsonSerialize(): array {
|
|
||||||
return ["field" => $this->fieldName, "message" => $this->getMessage()];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Validation;
|
|
||||||
|
|
||||||
class FunctionValidator extends Validator {
|
|
||||||
/**
|
|
||||||
* @var callable(string, mixed): ValidationFail[]
|
|
||||||
*/
|
|
||||||
private $validate_fn;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(string, mixed): ValidationFail[] $validate_fn the validate function. Must have the same signature as the {@link Validator::validate()} method.
|
|
||||||
*/
|
|
||||||
public function __construct(callable $validate_fn) {
|
|
||||||
$this->validate_fn = $validate_fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validate(string $name, $val): array {
|
|
||||||
return call_user_func_array($this->validate_fn, [$name, $val]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Validation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple validator that takes a predicate and an error factory
|
|
||||||
*/
|
|
||||||
class SimpleFunctionValidator extends Validator {
|
|
||||||
/**
|
|
||||||
* @var callable(mixed): bool
|
|
||||||
*/
|
|
||||||
private $predicate;
|
|
||||||
/**
|
|
||||||
* @var callable(string): ValidationFail[]
|
|
||||||
*/
|
|
||||||
private $errorFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callable(mixed): bool $predicate a function predicate with signature: `(string) => bool`, to validate the given string
|
|
||||||
* @param callable(string): ValidationFail[] $errorsFactory a factory function with signature `(string) => array` to emit failures when the predicate fails
|
|
||||||
*/
|
|
||||||
public function __construct(callable $predicate, callable $errorsFactory) {
|
|
||||||
$this->predicate = $predicate;
|
|
||||||
$this->errorFactory = $errorsFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validate(string $name, $val): array {
|
|
||||||
if (!call_user_func_array($this->predicate, [$val])) {
|
|
||||||
return call_user_func_array($this->errorFactory, [$name]);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Validation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for validation
|
|
||||||
*/
|
|
||||||
class Validation {
|
|
||||||
/**
|
|
||||||
* Validate a value from validators, appending failures in the given errors array.
|
|
||||||
* @param mixed $val the value to validate
|
|
||||||
* @param string $valName the name of the value
|
|
||||||
* @param ValidationFail[] $failures array to push when a validator fails
|
|
||||||
* @param Validator ...$validators given validators
|
|
||||||
* @return bool true if any of the given validators did fail
|
|
||||||
*/
|
|
||||||
public static function validate($val, string $valName, array &$failures, Validator...$validators): bool {
|
|
||||||
$had_errors = false;
|
|
||||||
foreach ($validators as $validator) {
|
|
||||||
$error = $validator->validate($valName, $val);
|
|
||||||
if ($error != null) {
|
|
||||||
$failures = array_merge($failures, $error);
|
|
||||||
$had_errors = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $had_errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Validation;
|
|
||||||
|
|
||||||
use JsonSerializable;
|
|
||||||
|
|
||||||
class ValidationFail implements JsonSerializable {
|
|
||||||
private string $kind;
|
|
||||||
|
|
||||||
private string $message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $message
|
|
||||||
* @param string $kind
|
|
||||||
*/
|
|
||||||
public function __construct(string $kind, string $message) {
|
|
||||||
$this->message = $message;
|
|
||||||
$this->kind = $kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMessage(): string {
|
|
||||||
return $this->message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getKind(): string {
|
|
||||||
return $this->kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
public function jsonSerialize(): array {
|
|
||||||
return ["error" => $this->kind, "message" => $this->message];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $message
|
|
||||||
* @return ValidationFail validation fail for unknown resource access
|
|
||||||
*/
|
|
||||||
public static function notFound(string $message): ValidationFail {
|
|
||||||
return new ValidationFail("Not found", $message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $message
|
|
||||||
* @return ValidationFail validation fail for unauthorized accesses
|
|
||||||
*/
|
|
||||||
public static function unauthorized(string $message = "Unauthorized"): ValidationFail {
|
|
||||||
return new ValidationFail("Unauthorized", $message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function error(string $message): ValidationFail {
|
|
||||||
return new ValidationFail("Error", $message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace IQBall\Core\Validation;
|
|
||||||
|
|
||||||
abstract class Validator {
|
|
||||||
/**
|
|
||||||
* validates a variable string
|
|
||||||
* @param string $name the name of the tested value
|
|
||||||
* @param mixed $val the value to validate
|
|
||||||
* @return ValidationFail[] the errors the validator has reported
|
|
||||||
*/
|
|
||||||
abstract public function validate(string $name, $val): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a validator composed of this validator, and given validator
|
|
||||||
* @param Validator $other the second validator to validate if this validator succeeded
|
|
||||||
* @return Validator a composed validator
|
|
||||||
*/
|
|
||||||
public function then(Validator $other): Validator {
|
|
||||||
return new ComposedValidator($this, $other);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Before Width: | Height: | Size: 747 B After Width: | Height: | Size: 747 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 507 B After Width: | Height: | Size: 507 B |
Before Width: | Height: | Size: 732 B After Width: | Height: | Size: 732 B |
After Width: | Height: | Size: 335 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 405 B |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |