Merged in feature/MAPG-115-guard-endpoints-based-on-user-g (pull request #89)
Feature/MAPG-115 guard endpoints based on user g
This commit is contained in:
commit
2f2a7968a0
8
database/migrations/structure/20200606_2352_users.sql
Normal file
8
database/migrations/structure/20200606_2352_users.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE `users` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`email` varchar(100) NOT NULL,
|
||||||
|
`password` varchar(60) NOT NULL,
|
||||||
|
`type` enum('user', 'admin') NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `email` (`email`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
1
mapg
1
mapg
@ -6,5 +6,6 @@ require 'main.php';
|
|||||||
$app = new Symfony\Component\Console\Application('MapGuesser Console', '');
|
$app = new Symfony\Component\Console\Application('MapGuesser Console', '');
|
||||||
|
|
||||||
$app->add(new MapGuesser\Cli\DatabaseMigration());
|
$app->add(new MapGuesser\Cli\DatabaseMigration());
|
||||||
|
$app->add(new MapGuesser\Cli\AddUserCommand());
|
||||||
|
|
||||||
$app->run();
|
$app->run();
|
||||||
|
@ -12,6 +12,9 @@ if (($pos = strpos($url, '?')) !== false) {
|
|||||||
$url = rawurldecode($url);
|
$url = rawurldecode($url);
|
||||||
|
|
||||||
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
|
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
|
||||||
|
Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
|
||||||
|
Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']);
|
||||||
|
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
|
||||||
Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
|
Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
|
||||||
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
|
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
|
||||||
@ -30,15 +33,32 @@ $match = Container::$routeCollection->match($method, explode('/', $url));
|
|||||||
if ($match !== null) {
|
if ($match !== null) {
|
||||||
list($route, $params) = $match;
|
list($route, $params) = $match;
|
||||||
|
|
||||||
$response = $route->callController($params);
|
$request = new MapGuesser\Request\Request($_GET, $params, $_POST, $_SESSION);
|
||||||
|
|
||||||
|
$handler = $route->getHandler();
|
||||||
|
$controller = new $handler[0]($request);
|
||||||
|
|
||||||
|
if ($controller instanceof MapGuesser\Interfaces\Authorization\ISecured) {
|
||||||
|
$authorized = $controller->authorize();
|
||||||
|
} else {
|
||||||
|
$authorized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($authorized) {
|
||||||
|
$response = call_user_func([$controller, $handler[1]]);
|
||||||
|
|
||||||
if ($response instanceof MapGuesser\Interfaces\Response\IContent) {
|
if ($response instanceof MapGuesser\Interfaces\Response\IContent) {
|
||||||
header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
|
header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
|
||||||
echo $response->render();
|
echo $response->render();
|
||||||
|
|
||||||
|
return;
|
||||||
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
||||||
header('Location: ' . $host . '/' . $response->getUrl(), true, $response->getHttpCode());
|
header('Location: ' . $host . '/' . $response->getUrl(), true, $response->getHttpCode());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
header('Content-Type: text/html; charset=UTF-8', true, 404);
|
|
||||||
require ROOT . '/views/error/404.php';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header('Content-Type: text/html; charset=UTF-8', true, 404);
|
||||||
|
require ROOT . '/views/error/404.php';
|
||||||
|
@ -17,7 +17,7 @@ button::-moz-focus-inner, input::-moz-focus-inner {
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
p, h1, h2, button, a {
|
p, h1, h2, input, textarea, select, button, a {
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +93,10 @@ sub {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
svg.inline, img.inline {
|
svg.inline, img.inline {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
@ -158,6 +162,54 @@ button.red:hover, button.red:focus, a.button.red:hover, a.button.red:focus {
|
|||||||
background-color: #7f2929;
|
background-color: #7f2929;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input, select, textarea {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
border: solid #c8d2e1 1px;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font-size: 13px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.big, select.big, textarea.big {
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.fullWidth, select.fullWidth, textarea.fullWidth {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled, select:disabled, textarea:disabled {
|
||||||
|
background-color: #dfdfdf;
|
||||||
|
border: solid #dfdfdf 1px;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus, select:focus, textarea:focus {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: solid #29457f 2px;
|
||||||
|
padding: 3px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.big:focus, select.big:focus, textarea.big:focus {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.formError {
|
||||||
|
color: #7f2929;
|
||||||
|
font-weight: 500;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
div.header {
|
div.header {
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
@ -200,6 +252,15 @@ div.buttonContainer>button {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.box {
|
||||||
|
width: 576px;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 10px auto;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
div.header.small h1 span {
|
div.header.small h1 span {
|
||||||
display: none;
|
display: none;
|
||||||
@ -208,4 +269,7 @@ div.buttonContainer>button {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
div.box {
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
42
public/static/js/login.js
Normal file
42
public/static/js/login.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
(function () {
|
||||||
|
var form = document.getElementById('loginForm');
|
||||||
|
|
||||||
|
form.onsubmit = function (e) {
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var formData = new FormData(form);
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.onload = function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
if (this.response.error) {
|
||||||
|
var errorText;
|
||||||
|
switch (this.response.error) {
|
||||||
|
case 'user_not_found':
|
||||||
|
errorText = 'No user found with the given email address.';
|
||||||
|
break;
|
||||||
|
case 'password_not_match':
|
||||||
|
errorText = 'The given password is wrong.'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginFormError = document.getElementById('loginFormError');
|
||||||
|
loginFormError.style.display = 'block';
|
||||||
|
loginFormError.innerHTML = errorText;
|
||||||
|
|
||||||
|
form.elements.email.select();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.replace('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.open('POST', form.action, true);
|
||||||
|
xhr.send(formData);
|
||||||
|
};
|
||||||
|
})();
|
51
src/Cli/AddUserCommand.php
Normal file
51
src/Cli/AddUserCommand.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
|
use MapGuesser\Model\User;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class AddUserCommand extends Command
|
||||||
|
{
|
||||||
|
public function configure()
|
||||||
|
{
|
||||||
|
$this->setName('user:add')
|
||||||
|
->setDescription('Adding of user.')
|
||||||
|
->addArgument('email', InputArgument::REQUIRED, 'Email of user')
|
||||||
|
->addArgument('password', InputArgument::REQUIRED, 'Password of user')
|
||||||
|
->addArgument('type', InputArgument::OPTIONAL, 'Type of user');;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$user = new User([
|
||||||
|
'email' => $input->getArgument('email'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->setPlainPassword($input->getArgument('password'));
|
||||||
|
|
||||||
|
if ($input->hasArgument('type')) {
|
||||||
|
$user->setType($input->getArgument('type'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->fill($user->toArray());
|
||||||
|
$modify->save();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln('<error>Adding user failed!</error>');
|
||||||
|
$output->writeln('');
|
||||||
|
|
||||||
|
$output->writeln((string) $e);
|
||||||
|
$output->writeln('');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln('<info>User was successfully added!</info>');
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Util\Geo\Bounds;
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
@ -9,16 +10,23 @@ use MapGuesser\Interfaces\Response\IContent;
|
|||||||
|
|
||||||
class GameController
|
class GameController
|
||||||
{
|
{
|
||||||
public function getGame(array $parameters): IContent
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$mapId = (int) $parameters['mapId'];
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGame(): IContent
|
||||||
|
{
|
||||||
|
$mapId = (int) $this->request->query('mapId');
|
||||||
$data = $this->prepareGame($mapId);
|
$data = $this->prepareGame($mapId);
|
||||||
return new HtmlContent('game', $data);
|
return new HtmlContent('game', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGameJson(array $parameters): IContent
|
public function getGameJson(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $parameters['mapId'];
|
$mapId = (int) $this->request->query('mapId');
|
||||||
$data = $this->prepareGame($mapId);
|
$data = $this->prepareGame($mapId);
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
@ -27,12 +35,14 @@ class GameController
|
|||||||
{
|
{
|
||||||
$bounds = $this->getMapBounds($mapId);
|
$bounds = $this->getMapBounds($mapId);
|
||||||
|
|
||||||
if (!isset($_SESSION['state']) || $_SESSION['state']['mapId'] !== $mapId) {
|
$session = $this->request->session();
|
||||||
$_SESSION['state'] = [
|
|
||||||
|
if (($state = $session->get('state')) && $state['mapId'] !== $mapId) {
|
||||||
|
$session->set('state', [
|
||||||
'mapId' => $mapId,
|
'mapId' => $mapId,
|
||||||
'area' => $bounds->calculateApproximateArea(),
|
'area' => $bounds->calculateApproximateArea(),
|
||||||
'rounds' => []
|
'rounds' => []
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['mapId' => $mapId, 'bounds' => $bounds->toArray()];
|
return ['mapId' => $mapId, 'bounds' => $bounds->toArray()];
|
||||||
|
73
src/Controller/LoginController.php
Normal file
73
src/Controller/LoginController.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
|
use MapGuesser\Model\User;
|
||||||
|
use MapGuesser\Response\HtmlContent;
|
||||||
|
use MapGuesser\Response\JsonContent;
|
||||||
|
use MapGuesser\Response\Redirect;
|
||||||
|
|
||||||
|
class LoginController
|
||||||
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLoginForm()
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
return new HtmlContent('login', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function login(): IContent
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
$data = ['success' => true];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'users');
|
||||||
|
$select->columns(User::getFields());
|
||||||
|
$select->where('email', '=', $this->request->post('email'));
|
||||||
|
|
||||||
|
$userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($userData === null) {
|
||||||
|
$data = ['error' => 'user_not_found'];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = new User($userData);
|
||||||
|
|
||||||
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
|
$data = ['error' => 'password_not_match'];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$session->set('user', $user);
|
||||||
|
|
||||||
|
$data = ['success' => true];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logout(): IRedirect
|
||||||
|
{
|
||||||
|
$this->request->session()->delete('user');
|
||||||
|
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('login'), []], IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,35 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use MapGuesser\Util\Geo\Bounds;
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
|
|
||||||
class MapAdminController
|
class MapAdminController implements ISecured
|
||||||
{
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
private PlaceRepository $placeRepository;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
|
$this->request = $request;
|
||||||
$this->placeRepository = new PlaceRepository();
|
$this->placeRepository = new PlaceRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
return $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN);
|
||||||
|
}
|
||||||
|
|
||||||
public function getMaps(): IContent
|
public function getMaps(): IContent
|
||||||
{
|
{
|
||||||
//TODO
|
//TODO
|
||||||
@ -24,9 +37,9 @@ class MapAdminController
|
|||||||
return new HtmlContent('admin/maps');
|
return new HtmlContent('admin/maps');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMapEditor(array $parameters): IContent
|
public function getMapEditor(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $parameters['mapId'];
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
$bounds = $this->getMapBounds($mapId);
|
$bounds = $this->getMapBounds($mapId);
|
||||||
|
|
||||||
@ -36,9 +49,9 @@ class MapAdminController
|
|||||||
return new HtmlContent('admin/map_editor', $data);
|
return new HtmlContent('admin/map_editor', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPlace(array $parameters)
|
public function getPlace()
|
||||||
{
|
{
|
||||||
$placeId = (int) $parameters['placeId'];
|
$placeId = (int) $this->request->query('placeId');
|
||||||
|
|
||||||
$placeData = $this->placeRepository->getById($placeId);
|
$placeData = $this->placeRepository->getById($placeId);
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Util\Geo\Position;
|
use MapGuesser\Util\Geo\Position;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
@ -10,34 +11,40 @@ class PositionController
|
|||||||
const NUMBER_OF_ROUNDS = 5;
|
const NUMBER_OF_ROUNDS = 5;
|
||||||
const MAX_SCORE = 1000;
|
const MAX_SCORE = 1000;
|
||||||
|
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
private PlaceRepository $placeRepository;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
|
$this->request = $request;
|
||||||
$this->placeRepository = new PlaceRepository();
|
$this->placeRepository = new PlaceRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPosition(array $parameters): IContent
|
public function getPosition(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $parameters['mapId'];
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
if (!isset($_SESSION['state']) || $_SESSION['state']['mapId'] !== $mapId) {
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
||||||
$data = ['error' => 'no_session_found'];
|
$data = ['error' => 'no_session_found'];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($_SESSION['state']['rounds']) === 0) {
|
if (count($state['rounds']) === 0) {
|
||||||
$newPosition = $this->placeRepository->getForMapWithValidPano($mapId);
|
$newPosition = $this->placeRepository->getForMapWithValidPano($mapId);
|
||||||
$_SESSION['state']['rounds'][] = $newPosition;
|
$state['rounds'][] = $newPosition;
|
||||||
|
$session->set('state', $state);
|
||||||
|
|
||||||
$data = ['panoId' => $newPosition['panoId']];
|
$data = ['panoId' => $newPosition['panoId']];
|
||||||
} else {
|
} else {
|
||||||
$rounds = count($_SESSION['state']['rounds']);
|
$rounds = count($state['rounds']);
|
||||||
$last = $_SESSION['state']['rounds'][$rounds - 1];
|
$last = $state['rounds'][$rounds - 1];
|
||||||
|
|
||||||
$history = [];
|
$history = [];
|
||||||
for ($i = 0; $i < $rounds - 1; ++$i) {
|
for ($i = 0; $i < $rounds - 1; ++$i) {
|
||||||
$round = $_SESSION['state']['rounds'][$i];
|
$round = $state['rounds'][$i];
|
||||||
$history[] = [
|
$history[] = [
|
||||||
'position' => $round['position']->toArray(),
|
'position' => $round['position']->toArray(),
|
||||||
'guessPosition' => $round['guessPosition']->toArray(),
|
'guessPosition' => $round['guessPosition']->toArray(),
|
||||||
@ -55,41 +62,45 @@ class PositionController
|
|||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function evaluateGuess(array $parameters): IContent
|
public function evaluateGuess(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $parameters['mapId'];
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
if (!isset($_SESSION['state']) || $_SESSION['state']['mapId'] !== $mapId) {
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
||||||
$data = ['error' => 'no_session_found'];
|
$data = ['error' => 'no_session_found'];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$last = &$_SESSION['state']['rounds'][count($_SESSION['state']['rounds']) - 1];
|
$last = $state['rounds'][count($state['rounds']) - 1];
|
||||||
|
|
||||||
$position = $last['position'];
|
$position = $last['position'];
|
||||||
$guessPosition = new Position((float) $_POST['lat'], (float) $_POST['lng']);
|
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
|
||||||
|
|
||||||
$last['guessPosition'] = $guessPosition;
|
|
||||||
|
|
||||||
$distance = $this->calculateDistance($position, $guessPosition);
|
$distance = $this->calculateDistance($position, $guessPosition);
|
||||||
$score = $this->calculateScore($distance, $_SESSION['state']['area']);
|
$score = $this->calculateScore($distance, $state['area']);
|
||||||
|
|
||||||
|
$last['guessPosition'] = $guessPosition;
|
||||||
$last['distance'] = $distance;
|
$last['distance'] = $distance;
|
||||||
$last['score'] = $score;
|
$last['score'] = $score;
|
||||||
|
$state['rounds'][count($state['rounds']) - 1] = $last;
|
||||||
|
|
||||||
if (count($_SESSION['state']['rounds']) < static::NUMBER_OF_ROUNDS) {
|
if (count($state['rounds']) < static::NUMBER_OF_ROUNDS) {
|
||||||
$exclude = [];
|
$exclude = [];
|
||||||
|
|
||||||
foreach ($_SESSION['state']['rounds'] as $round) {
|
foreach ($state['rounds'] as $round) {
|
||||||
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
|
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$newPosition = $this->placeRepository->getForMapWithValidPano($mapId, $exclude);
|
$newPosition = $this->placeRepository->getForMapWithValidPano($mapId, $exclude);
|
||||||
$_SESSION['state']['rounds'][] = $newPosition;
|
$state['rounds'][] = $newPosition;
|
||||||
|
$session->set('state', $state);
|
||||||
|
|
||||||
$panoId = $newPosition['panoId'];
|
$panoId = $newPosition['panoId'];
|
||||||
} else {
|
} else {
|
||||||
$_SESSION['state']['rounds'] = [];
|
$state['rounds'] = [];
|
||||||
|
$session->set('state', $state);
|
||||||
|
|
||||||
$panoId = null;
|
$panoId = null;
|
||||||
}
|
}
|
||||||
|
10
src/Interfaces/Authentication/IUser.php
Normal file
10
src/Interfaces/Authentication/IUser.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Authentication;
|
||||||
|
|
||||||
|
interface IUser
|
||||||
|
{
|
||||||
|
const PERMISSION_NORMAL = 0;
|
||||||
|
|
||||||
|
const PERMISSION_ADMIN = 1;
|
||||||
|
|
||||||
|
public function hasPermission(int $permission): bool;
|
||||||
|
}
|
6
src/Interfaces/Authorization/ISecured.php
Normal file
6
src/Interfaces/Authorization/ISecured.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Authorization;
|
||||||
|
|
||||||
|
interface ISecured
|
||||||
|
{
|
||||||
|
public function authorize(): bool;
|
||||||
|
}
|
14
src/Interfaces/Request/IRequest.php
Normal file
14
src/Interfaces/Request/IRequest.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Request;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
|
||||||
|
interface IRequest
|
||||||
|
{
|
||||||
|
public function query(string $key);
|
||||||
|
|
||||||
|
public function post(string $key);
|
||||||
|
|
||||||
|
public function session(): ISession;
|
||||||
|
|
||||||
|
public function user(): ?IUser;
|
||||||
|
}
|
12
src/Interfaces/Request/ISession.php
Normal file
12
src/Interfaces/Request/ISession.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Request;
|
||||||
|
|
||||||
|
interface ISession
|
||||||
|
{
|
||||||
|
public function has(string $key): bool;
|
||||||
|
|
||||||
|
public function get(string $key);
|
||||||
|
|
||||||
|
public function set(string $key, $value): void;
|
||||||
|
|
||||||
|
public function delete(string $key): void;
|
||||||
|
}
|
49
src/Model/BaseModel.php
Normal file
49
src/Model/BaseModel.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php namespace MapGuesser\Model;
|
||||||
|
|
||||||
|
abstract class BaseModel
|
||||||
|
{
|
||||||
|
protected static array $fields;
|
||||||
|
|
||||||
|
protected $id = null;
|
||||||
|
|
||||||
|
public static function getFields(): array
|
||||||
|
{
|
||||||
|
return array_merge(['id'], static::$fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
|
||||||
|
|
||||||
|
if (method_exists($this, $method)) {
|
||||||
|
$this->$method($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setId($id): void
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toArray(): array
|
||||||
|
{
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
foreach (self::getFields() as $key) {
|
||||||
|
$method = 'get' . str_replace('_', '', ucwords($key, '_'));
|
||||||
|
|
||||||
|
if (method_exists($this, $method)) {
|
||||||
|
$array[$key] = $this->$method();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
}
|
70
src/Model/User.php
Normal file
70
src/Model/User.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php namespace MapGuesser\Model;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
|
||||||
|
class User extends BaseModel implements IUser
|
||||||
|
{
|
||||||
|
private static array $types = ['user', 'admin'];
|
||||||
|
|
||||||
|
protected static array $fields = ['email', 'password', 'type'];
|
||||||
|
|
||||||
|
private string $email;
|
||||||
|
|
||||||
|
private string $password;
|
||||||
|
|
||||||
|
private string $type = 'user';
|
||||||
|
|
||||||
|
public function setEmail(string $email): void
|
||||||
|
{
|
||||||
|
$this->email = $email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPassword(string $hashedPassword): void
|
||||||
|
{
|
||||||
|
$this->password = $hashedPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPlainPassword(string $plainPassword): void
|
||||||
|
{
|
||||||
|
$this->password = password_hash($plainPassword, PASSWORD_BCRYPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setType(string $type): void
|
||||||
|
{
|
||||||
|
if (in_array($type, self::$types)) {
|
||||||
|
$this->type = $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEmail(): string
|
||||||
|
{
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassword(): string
|
||||||
|
{
|
||||||
|
return $this->password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasPermission(int $permission): bool
|
||||||
|
{
|
||||||
|
switch ($permission) {
|
||||||
|
case IUser::PERMISSION_NORMAL:
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case IUser::PERMISSION_ADMIN:
|
||||||
|
return $this->type === 'admin';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkPassword(string $password): bool
|
||||||
|
{
|
||||||
|
return password_verify($password, $this->password);
|
||||||
|
}
|
||||||
|
}
|
57
src/Request/Request.php
Normal file
57
src/Request/Request.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php namespace MapGuesser\Request;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Request\ISession;
|
||||||
|
use MapGuesser\Model\User;
|
||||||
|
|
||||||
|
class Request implements IRequest
|
||||||
|
{
|
||||||
|
private array $get;
|
||||||
|
|
||||||
|
private array $routeParams;
|
||||||
|
|
||||||
|
private array $post;
|
||||||
|
|
||||||
|
private Session $session;
|
||||||
|
|
||||||
|
public function __construct(array &$get, array &$routeParams, array &$post, array &$session)
|
||||||
|
{
|
||||||
|
$this->get = &$get;
|
||||||
|
$this->routeParams = &$routeParams;
|
||||||
|
$this->post = &$post;
|
||||||
|
$this->session = new Session($session);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function query($key)
|
||||||
|
{
|
||||||
|
if (isset($this->get[$key])) {
|
||||||
|
return $this->get[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->routeParams[$key])) {
|
||||||
|
return $this->routeParams[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function post($key)
|
||||||
|
{
|
||||||
|
if (isset($this->post[$key])) {
|
||||||
|
return $this->post[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function session(): ISession
|
||||||
|
{
|
||||||
|
return $this->session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): ?IUser
|
||||||
|
{
|
||||||
|
return $this->session->get('user');
|
||||||
|
}
|
||||||
|
}
|
37
src/Request/Session.php
Normal file
37
src/Request/Session.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php namespace MapGuesser\Request;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Request\ISession;
|
||||||
|
|
||||||
|
class Session implements ISession
|
||||||
|
{
|
||||||
|
private array $data;
|
||||||
|
|
||||||
|
public function __construct(array &$data)
|
||||||
|
{
|
||||||
|
$this->data = &$data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function has($key): bool
|
||||||
|
{
|
||||||
|
return isset($this->data[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($key)
|
||||||
|
{
|
||||||
|
if (isset($this->data[$key])) {
|
||||||
|
return $this->data[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set($key, $value): void
|
||||||
|
{
|
||||||
|
$this->data[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($key): void
|
||||||
|
{
|
||||||
|
unset($this->data[$key]);
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,11 @@ class Route
|
|||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHandler(): array
|
||||||
|
{
|
||||||
|
return $this->handler;
|
||||||
|
}
|
||||||
|
|
||||||
public function generateLink(array $parameters = []): string
|
public function generateLink(array $parameters = []): string
|
||||||
{
|
{
|
||||||
$link = [];
|
$link = [];
|
||||||
@ -51,14 +56,6 @@ class Route
|
|||||||
return implode('/', $link) . $query;
|
return implode('/', $link) . $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function callController(array $parameters)
|
|
||||||
{
|
|
||||||
$controllerName = $this->handler[0];
|
|
||||||
$controller = new $controllerName();
|
|
||||||
|
|
||||||
return call_user_func([$controller, $this->handler[1]], $parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testAgainst(array $path): ?array
|
public function testAgainst(array $path): ?array
|
||||||
{
|
{
|
||||||
$parameters = [];
|
$parameters = [];
|
||||||
|
17
views/login.php
Normal file
17
views/login.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
|
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||||
|
<div class="main">
|
||||||
|
<h2>Login</h2>
|
||||||
|
<div class="box">
|
||||||
|
<form id="loginForm" action="/login" method="post">
|
||||||
|
<input class="big fullWidth" type="email" name="email" placeholder="Email address" autofocus>
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password">
|
||||||
|
<p id="loginFormError" class="formError marginTop"></p>
|
||||||
|
<div class="right marginTop">
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="/static/js/login.js"></script>
|
||||||
|
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
Loading…
Reference in New Issue
Block a user