Merge pull request #8 from Sundowndev/develop

[API] Authentication and Task resource
master 1.0.0-rc1
Raphael Cerveaux 2018-07-26 16:42:49 +02:00 committed by GitHub
commit 9bba703c7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 958 additions and 183 deletions

View File

@ -34,12 +34,14 @@ CMD ["php-fpm"]
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
ENV COMPOSER_ALLOW_SUPERUSER 1
# Use prestissimo to speed up builds
RUN composer global require "hirak/prestissimo:^0.3" --prefer-dist --no-progress --no-suggest --optimize-autoloader --classmap-authoritative --no-interaction
###> recipes ###
###< recipes ###
COPY ./server .
RUN composer install
# Use prestissimo to speed up builds
RUN composer global require "hirak/prestissimo:^0.3" --prefer-dist --no-progress --no-suggest --optimize-autoloader --classmap-authoritative --no-interaction
RUN composer install
RUN echo $(cat .env.dist) > .env

View File

@ -1,33 +1,31 @@
# interview-v1
![](https://api.travis-ci.org/Sundowndev/interview-v1.svg)
Build status : ![](https://api.travis-ci.org/Sundowndev/interview-v1.svg)
## Description
1/ Développer en PHP une mini API REST avec output en json
1/ Develop a mini PHP REST API with json output
Cette api doit:
This api must manage 2 objects :
- User (id, name, email)
- Task (id, user_id, title, description, creation_date, status)
- Gérer 2 types d'objets:
User (id, name, email)
Task (id, user_id, title, description, creation_date, status)
Create API endpoints to recover a user or task data. (e.g /user/{id})
- Mettre à disposition des endpoints permettant de récupérer les données d'un user et d'une task. (ex: /user/$id)
The API must be able to manage users tasks and create the endpoints to:
- Fetch the latest tasks
- Create a task
- Delete a task
- L'api doit être capable de manipuler la liste des taches associées à un utilisateur en offrant la possibilité de:
Récupérer cette liste de taches
Créer et ajouter une nouvelle tache
Supprimer une tache
While developing this API, you must keep in mind it can evolve at any moment (new resources, new properties in objects ...).
En développant cette API, vous devez garder en tête qu'elle est susceptible d'évoluer (nouveaux retours, nouveaux attributs dans les objets)
2/ Create a frontend client to call the API
2/ Développer un front en HtML/JS/CSS (pas de design nécessaire)
- The client must call the api using ajax
- We must be able to create/delete an user
- Manage user's tasks (read / add / delete)
Ce front doit communiquer avec l'api en ajax.
On doit pouvoir ajouter/supprimer un utilisateur
Gérer la liste des tâches d'un utilisateur (liste / ajout / suppression)
(pas de framework)
(no framework)
## Installation and usage
@ -51,11 +49,21 @@ The architecture is made of a simple client -> server communication using Docker
## Security
To handle authentication feature, we use JWT authentication.
JSON Web Token (JWT) is an open standard ([RFC 7519](https://tools.ietf.org/html/rfc7519)) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA. [Source](https://jwt.io/introduction/)
As soon as the user provide valid credentials, we return a JWT token that will be needed for each request the client will send to the API.
For each request, the user send the JWT token as parameter.
![JWT explained](https://cdn-images-1.medium.com/max/1400/1*SSXUQJ1dWjiUrDoKaaiGLA.png)
## API endpoints
| Method / Route | Resource | Description |
| --------------------- | ------------------ | ------------ |
| `POST` /auth | Authentification | Connect and get an api key |
| `POST` /auth | Authentication | Connect and get an api key |
| `GET` /tasks | Task | Get latest taks |
| `GET` /tasks/{id} | Task | Get a task by given id |
| `POST` /tasks | Task | Create a task |

View File

@ -7,7 +7,7 @@ services:
- "3311:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: mysql
MYSQL_DATABASE: app1
MYSQL_USER: mysql
MYSQL_PASSWORD: mysql
@ -42,3 +42,10 @@ services:
- ./docker/httpd/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro
ports:
- '443:443'
# For development purposes
adminer:
image: adminer
restart: always
ports:
- 8080:8080

View File

@ -1,8 +1,9 @@
### Environment file ###
MYSQL_USER=mysql
MYSQL_PASS=mysql
MYSQL_USER=root
MYSQL_PASS=root
MYSQL_HOST=db
MYSQL_PORT=3311
MYSQL_DBNAME=mysql
MYSQL_PORT=3306
MYSQL_DBNAME=app1
### Allow origin for the API ###
ALLOW_ORIGIN=^https?://localhost:?[0-9]*$
ALLOW_ORIGIN=^https?://localhost:?[0-9]*$
APP_SECRET=04980744f74f4ec36ad5a9d5fec8876f

View File

@ -1,32 +1,84 @@
<?php
use App\Service\DotEnvParser;
use App\Service\JsonResponse;
$router->setNamespace('\App\Controller');
$router->before('GET|POST', '/.*', function() use ($router) {
$router->before('GET|POST|PUT|DELETE', '/.*', function () use ($router) {
# This will be always executed
$dotEnvParser = new DotEnvParser();
$dotEnvParser->run();
$jsonResponse = new JsonResponse();
if ($_SERVER['HTTP_ACCEPT'] !== 'application/json') {
$code = 400;
$message = 'Accept header is not set to "application/json".';
print $jsonResponse->create($code, $message, []);
exit();
} elseif ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['CONTENT_TYPE'] !== 'application/json') {
$code = 400;
$message = 'Content-type header is not set to "application/json".';
print $jsonResponse->create($code, $message, []);
exit();
}/* elseif ($_SERVER['HTTP_ORIGIN'] !== getenv('ALLOW_ORIGIN')) {
$code = 403;
$message = 'Unallowed origin.';
print $jsonResponse->create($code, $message, []);
exit();
}*/
});
/**
* API index
*/
$router->get('/', 'DefaultController@index');
/**
* 404 error response
*/
$router->set404('DefaultController@error');
/**
* Session handling routes
*/
$router->post('/auth', 'SessionController@auth');
$router->post('/signup', 'SessionController@signup');
$router->post('/logout', 'SessionController@signout');
$router->get('/me', 'SessionController@me');
/**
* Task resource
*/
$router->mount('/tasks', function () use ($router) {
// get all tasks
// Get all tasks
$router->get('/', 'TaskController@getAll');
// get one task
// Get one task
$router->get('/(\d+)', 'TaskController@get');
// create a task
// Create a task
$router->post('/', 'TaskController@post');
// update a task
// Update a task
$router->put('/(\d+)', 'TaskController@put');
// delete a task
// Delete a task
$router->delete('/(\d+)', 'TaskController@delete');
});
/**
* User resource
*/
$router->mount('/users', function () use ($router) {
// endpoints for user
// Create user (register)
$router->post('/', 'DefaultController@index');
// Get one user
$router->get('/(\d+)', 'DefaultController@index');
// Get one task's tasks
$router->get('/(\d+)/tasks', 'DefaultController@index');
});
$router->set404('DefaultController@error');

View File

@ -5,7 +5,7 @@
"license": "WTFPL",
"require": {
"bramus/router": "~1.3",
"josegonzalez/dotenv": "~3.2.0"
"codervio/envmanager": "^1.7"
},
"autoload": {
"psr-4": {

108
server/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "759257fb9b4837a4e886866e36a4c233",
"content-hash": "4ac0df3751e41700bc67b558c17957c9",
"packages": [
{
"name": "bramus/router",
@ -53,88 +53,29 @@
"time": "2017-12-21T20:37:23+00:00"
},
{
"name": "josegonzalez/dotenv",
"version": "dev-master",
"name": "codervio/envmanager",
"version": "1.7",
"source": {
"type": "git",
"url": "https://github.com/josegonzalez/php-dotenv.git",
"reference": "bc1677b827829f1e1db4a55b8283b4b836617499"
"url": "https://github.com/Codervio/Envmanager.git",
"reference": "6b74977f2a048fd2afac144032ccda2808ed2acb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/josegonzalez/php-dotenv/zipball/bc1677b827829f1e1db4a55b8283b4b836617499",
"reference": "bc1677b827829f1e1db4a55b8283b4b836617499",
"url": "https://api.github.com/repos/Codervio/Envmanager/zipball/6b74977f2a048fd2afac144032ccda2808ed2acb",
"reference": "6b74977f2a048fd2afac144032ccda2808ed2acb",
"shasum": ""
},
"require": {
"m1/env": "2.*",
"php": ">=5.5.0"
"php": "^7.0"
},
"require-dev": {
"php-mock/php-mock-phpunit": "^1.1",
"satooshi/php-coveralls": "1.*",
"squizlabs/php_codesniffer": "2.*"
},
"type": "library",
"autoload": {
"psr-0": {
"josegonzalez\\Dotenv": [
"src",
"tests"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jose Diaz-Gonzalez",
"email": "dotenv@josegonzalez.com",
"homepage": "http://josediazgonzalez.com",
"role": "Maintainer"
}
],
"description": "dotenv file parsing for PHP",
"homepage": "https://github.com/josegonzalez/php-dotenv",
"keywords": [
"configuration",
"dotenv",
"php"
],
"time": "2017-11-27T14:55:32+00:00"
},
{
"name": "m1/env",
"version": "2.1.2",
"source": {
"type": "git",
"url": "https://github.com/m1/Env.git",
"reference": "294addeedf15e1149eeb96ec829f2029d2017d39"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/m1/Env/zipball/294addeedf15e1149eeb96ec829f2029d2017d39",
"reference": "294addeedf15e1149eeb96ec829f2029d2017d39",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "4.*",
"scrutinizer/ocular": "~1.1",
"squizlabs/php_codesniffer": "^2.3"
},
"suggest": {
"josegonzalez/dotenv": "For loading of .env",
"m1/vars": "For loading of configs"
"phpunit/phpunit": "6.0.*"
},
"type": "library",
"autoload": {
"psr-4": {
"M1\\Env\\": "src"
"Codervio\\Envmanager\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -143,33 +84,38 @@
],
"authors": [
{
"name": "Miles Croxford",
"email": "hello@milescroxford.com",
"homepage": "http://milescroxford.com",
"role": "Developer"
"name": "Marin Sagovac",
"email": "marin@sagovac.com"
},
{
"name": "Codervio Community",
"homepage": "http://codervio.com"
}
],
"description": "Env is a lightweight library bringing .env file parser compatibility to PHP. In short - it enables you to read .env files with PHP.",
"homepage": "https://github.com/m1/Env",
"description": "Codervio Environment manager",
"homepage": "http://codervio.com",
"keywords": [
".env",
"array-env",
"config",
"dev-tools",
"dot-env",
"dotenv",
"dotenv-editor",
"env",
"environment",
"framework",
"loader",
"m1",
"parser",
"support"
"php"
],
"time": "2018-06-19T18:55:08+00:00"
"time": "2018-03-07T23:55:50+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"josegonzalez/dotenv": 20
},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],

View File

@ -0,0 +1,44 @@
-- Adminer 4.6.3 MySQL dump
SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
DROP DATABASE IF EXISTS `app1`;
CREATE DATABASE `app1` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `app1`;
DROP TABLE IF EXISTS `Session`;
CREATE TABLE `Session` (
`user_id` int(11) NOT NULL,
`token` varchar(255) NOT NULL,
`issued_at` datetime NOT NULL,
`expire_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
DROP TABLE IF EXISTS `Task`;
CREATE TABLE `Task` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`title` varchar(255) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`creation_date` datetime DEFAULT NULL,
`status` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `Task_id_uindex` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
DROP TABLE IF EXISTS `User`;
CREATE TABLE `User` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- 2018-07-22 00:34:19

View File

@ -0,0 +1,123 @@
<?php
namespace App\Controller;
use App\Repository\SessionRepository;
use App\Repository\UserRepository;
use App\Service\Database;
use App\Service\JsonResponse;
use App\Service\Request;
use App\Service\Session;
class SessionController
{
private $db;
private $jsonResponse;
private $sessionRepository;
private $request;
private $session;
private $security;
private $userRepository;
public function __construct()
{
$this->db = new Database();
$this->request = new Request();
$this->jsonResponse = new JsonResponse();
$this->session = new Session($this->db, $this->jsonResponse);
$this->security = $this->session->security;
$this->sessionRepository = new SessionRepository($this->db, $this->security);
$this->userRepository = new UserRepository($this->db);
}
/**
* Sign in route
*/
public function auth()
{
$body = $this->request->getContent()->jsonToArray();
if (empty($body['username']) || empty($body['password'])) {
print $this->jsonResponse->create(400, 'Please provide an username and password.');
exit();
}
$user = $this->userRepository->findOneByUsername($body['username']);
if (is_null($user) || !$this->security->passwordVerify($body['password'], $user['password'])) {
print $this->jsonResponse->create(403, 'Bad credentials.');
exit();
}
$token = $this->security->generateToken($user['id']);
$expire_at = new \DateTime();
$expire_at->modify('+1 Day'); // Expire in 1 day
$this->sessionRepository->create($user['id'], $token, $expire_at->format('Y-m-d H:i:s'));
print $this->jsonResponse->create(200, 'Welcome ' . $user['name'], [
'token' => $token,
'expire_at' => $expire_at,
]);
}
/**db
* Register route
*/
public function signup()
{
$body = $this->request->getContent()->jsonToArray();
if (empty($body['username']) || empty($body['email']) || empty($body['password'])) {
print $this->jsonResponse->create(400, 'Please provide an username, email and password.');
exit();
}
$user = [
'username' => $body['username'],
'email' => $body['email'],
'password' => $this->security->passwordHash($body['password']),
];
if (!is_null($this->userRepository->findOneByEmail($user['email']))) {
print $this->jsonResponse->create(403, 'Email already registered!');
exit();
}
$this->userRepository->create($user['username'], $user['email'], $user['password']);
print $this->jsonResponse->create(200, 'Success. Now send your credentials to /auth to sign in.', [
'username' => $user['username'],
'email' => $user['email'],
]);
}
/**
* Signout
*/
public function signout()
{
if (!$this->security->isLogged()) {
print $this->security->NotAllowedRequest();
exit();
}
$this->sessionRepository->deleteByToken($this->security->getBearerToken());
print $this->jsonResponse->create(200, 'Good bye.', []);
}
/**
* Whois route
*/
public function me()
{
if (!$this->security->isLogged()) {
print $this->security->NotAllowedRequest();
exit();
}
print $this->jsonResponse->create(200, 'hello!', $this->session->getUser());
}
}

View File

@ -5,23 +5,36 @@ namespace App\Controller;
use App\Service\JsonResponse;
use App\Service\Database;
use App\Repository\TaskRepository;
use App\Service\Request;
use App\Service\Session;
/**
* Class TaskController
* @package App\Controller
*/
class TaskController
{
private $jsonResponse;
private $db;
private $request;
private $jsonResponse;
private $session;
private $security;
private $repository;
public function __construct()
{
$this->jsonResponse = new JsonResponse();
$this->db = new Database();
$this->request = new Request();
$this->jsonResponse = new JsonResponse();
$this->repository = new TaskRepository($this->db);
$this->session = new Session($this->db, $this->jsonResponse);
$this->security = $this->session->security;
}
/**
* Get all tasks
*
* Route: /task
* Route: /tasks
* Method: GET
*/
public function getAll()
@ -34,9 +47,9 @@ class TaskController
}
/**
* Get all tasks
* Get task by id
*
* Route: /task/$id
* Route: /tasks/$id
* Method: GET
*/
public function get($id)
@ -45,22 +58,44 @@ class TaskController
$code = ($data != null) ? 200 : 404;
$message = ($data != null) ? "Task found." : "Task not found.";
//var_dump($data);
print $this->jsonResponse->create($code, $message, $data);
}
/**
* Create a task
*
* Route: /task
* Route: /tasks
* Method: POST
*/
public function post()
{
if (!$this->security->isLogged()) {
print $this->security->NotAllowedRequest();
exit();
}
$body = $this->request->getContent()->jsonToArray();
if (empty($body['title']) || empty($body['description'])) {
$code = 400;
$message = 'Bad parameters.';
print $this->jsonResponse->create($code, $message);
exit();
}
$user = $this->session->getUser();
$task = $this->repository->create([
'user_id' => $user['id'],
'title' => $body['title'],
'description' => $body['description'],
'status' => 1
]);
$code = 200;
$message = "";
$data = [];
$message = 'Success!';
$data = $task;
print $this->jsonResponse->create($code, $message, $data);
}
@ -68,14 +103,35 @@ class TaskController
/**
* Update a task
*
* Route: /task/$id
* Route: /tasks/$id
* Method: PUT
*/
public function put($id)
{
if (!$this->security->isLogged()) {
print $this->security->NotAllowedRequest();
exit();
}
$task = $this->repository->findOneById($id);
$user = $this->session->getUser();
if ($task['user_id'] !== $user['id']) {
print $this->security->NotAllowedRequest();
exit();
}
$body = $this->request->getContent()->jsonToArray();
$task = $this->repository->updateById($id, [
'title' => $body['title'] ?? $task['title'],
'description' => $body['description'] ?? $task['description'],
'status' => $body['status'] ?? $task['status']
]);
$code = 200;
$message = "";
$data = [];
$message = "Task edited.";
$data = $task;
print $this->jsonResponse->create($code, $message, $data);
}
@ -83,13 +139,28 @@ class TaskController
/**
* Delete a task
*
* Route: /task/$id
* Route: /tasks/$id
* Method: DELETE
*/
public function delete($id)
{
if (!$this->security->isLogged()) {
print $this->security->NotAllowedRequest();
exit();
}
$task = $this->repository->findOneById($id);
$user = $this->session->getUser();
if ($task['user_id'] !== $user['id']) {
print $this->security->NotAllowedRequest();
exit();
}
$this->repository->deleteById($id);
$code = 200;
$message = "";
$message = "Task deleted.";
$data = [];
print $this->jsonResponse->create($code, $message, $data);

View File

@ -0,0 +1,8 @@
<?php
namespace App\Controller;
class UserController
{
//
}

View File

@ -0,0 +1,142 @@
<?php
namespace App\Repository;
use App\Service\Database;
use App\Service\Security;
/**
* Class SessionRepository
* @package App\Repository
*/
class SessionRepository
{
/**
* @var Database
*/
private $db;
/**
* @var Security
*/
private $security;
/**
* @var
*/
private $tableName;
/**
* TaskRepository constructor.
* @param $db
*/
public function __construct($db, Security $security)
{
$this->db = $db;
$this->security = $security;
$this->tableName = 'Session';
}
/**
* @return mixed
*/
public function findAll()
{
$stmt = $this->db->getConnection()->prepare('SELECT * FROM ' . $this->tableName . ' ORDER BY id DESC');
$stmt->execute();
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* @param $id
* @return null
*/
public function findOneById($id)
{
$stmt = $this->db->getConnection()->prepare('SELECT * FROM ' . $this->tableName . ' WHERE id = :id');
$stmt->bindParam(':id', $id, \PDO::PARAM_INT);
$stmt->execute();
$task = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$task) {
return null;
} else {
return $task;
}
}
public function findUserBySessionToken($token)
{
$stmt = $this->db->getConnection()->prepare('SELECT s.token, s.expire_at, u.id, u.name, u.email FROM ' . $this->tableName . ' AS s INNER JOIN User as u ON s.user_id = u.id WHERE s.token = :token');
$stmt->bindParam(':token', $token, \PDO::PARAM_STR);
$stmt->execute();
return $stmt->fetch(\PDO::FETCH_ASSOC);
}
/**
* @param $user_id
* @param $csrf
* @param $cookie
*/
public function create($user_id, $token, $expiration)
{
$stmt = $this->db->getConnection()->prepare('INSERT INTO Session (`user_id`, `token`, `issued_at`, `expire_at`) VALUES(:user_id, :token, NOW(), :expire_at)');
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':token', $token);
$stmt->bindParam(':expire_at', $expiration);
$stmt->execute();
}
/**
* @param $id
* @param $data
* @return mixed
*/
public function updateById($id, $data)
{
$session = $this->findOneById($id);
/*$stmt = $this->db->getConnection()->prepare('UPDATE ' . $this->tableName . ' SET user_id = :user_id, title = :title, description = :description, creation_date = :creation_date, status = :status');
$stmt->bindParam(':user_id', $data['user_id'] ?? $task['user_id'], \PDO::PARAM_INT);
$stmt->bindParam(':title', $data['title'] ?? $task['title'], \PDO::PARAM_STR);
$stmt->bindParam(':description', $data['description'] ?? $task['description'], \PDO::PARAM_STR);
$stmt->bindParam(':creation_date', $data['creation_date'] ?? $task['creation_date']);
$stmt->bindParam(':status', $data['status'] ?? $task['status'], \PDO::PARAM_INT);
$stmt->execute();
return $data;*/
}
/**
* @param $id
*/
public function deleteById($id)
{
$stmt = $this->db->getConnection()->prepare('DELETE FROM ' . $this->tableName . ' WHERE id = :id');
$stmt->bindParam(':id', $id, \PDO::PARAM_INT);
$stmt->execute();
}
/**
* @param $token
*/
public function deleteByToken($token)
{
$stmt = $this->db->getConnection()->prepare('DELETE FROM ' . $this->tableName . ' WHERE token = :token');
$stmt->bindParam(':token', $token, \PDO::PARAM_STR);
$stmt->execute();
}
/**
* @param $userId
*/
public function deleteByUserId($userId)
{
$stmt = $this->db->getConnection()->prepare('DELETE FROM ' . $this->tableName . ' WHERE user_id = :user_id');
$stmt->bindParam(':user_id', $userId, \PDO::PARAM_INT);
$stmt->execute();
}
}

View File

@ -2,19 +2,35 @@
namespace App\Repository;
use App\Service\Database;
/**
* Class TaskRepository
* @package App\Repository
*/
class TaskRepository
{
/**
* @var Database
*/
private $db;
/**
* @var
*/
private $tableName;
/**
* TaskRepository constructor.
* @param $db
*/
public function __construct($db)
{
$this->db = $db;
$this->tableName = 'Task';
}
/**
* @return mixed
*/
public function findAll()
{
$stmt = $this->db->getConnection()->prepare('SELECT * FROM ' . $this->tableName . ' ORDER BY id DESC');
@ -23,6 +39,10 @@ class TaskRepository
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* @param $id
* @return null
*/
public function findOneById($id)
{
$stmt = $this->db->getConnection()->prepare('SELECT * FROM ' . $this->tableName . ' WHERE id = :id');
@ -38,18 +58,46 @@ class TaskRepository
}
}
/**
* @param $data
* @return mixed
*/
public function create($data)
{
//
$stmt = $this->db->getConnection()->prepare('INSERT INTO ' . $this->tableName . ' (user_id, title, description, creation_date, status) VALUES(:user_id, :title, :description, NOW(), :status)');
$stmt->bindParam(':user_id', $data['user_id'], \PDO::PARAM_INT);
$stmt->bindParam(':title', $data['title'], \PDO::PARAM_STR);
$stmt->bindParam(':description', $data['description'], \PDO::PARAM_STR);
$stmt->bindParam(':status', $data['status'], \PDO::PARAM_INT);
$stmt->execute();
return $data;
}
/**
* @param $id
* @param $data
* @return mixed
*/
public function updateById($id, $data)
{
//
$stmt = $this->db->getConnection()->prepare('UPDATE ' . $this->tableName . ' SET title = :title, description = :description, status = :status WHERE id = :id');
$stmt->bindParam(':id', $id, \PDO::PARAM_INT);
$stmt->bindParam(':title', $data['title'], \PDO::PARAM_STR);
$stmt->bindParam(':description', $data['description'], \PDO::PARAM_STR);
$stmt->bindParam(':status', $data['status'], \PDO::PARAM_INT);
$stmt->execute();
return $data;
}
/**
* @param $id
*/
public function deleteById($id)
{
//
$stmt = $this->db->getConnection()->prepare('DELETE FROM ' . $this->tableName . ' WHERE id = :id');
$stmt->bindParam(':id', $id, \PDO::PARAM_INT);
$stmt->execute();
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace App\Repository;
/**
* Class TaskRepository
* @package App\Repository
*/
class UserRepository
{
/**
* @var Database
*/
private $db;
/**
* @var
*/
private $tableName;
/**
* TaskRepository constructor.
* @param $db
*/
public function __construct($db)
{
$this->db = $db;
$this->tableName = 'User';
}
/**
* @return mixed
*/
public function findAll()
{
$stmt = $this->db->getConnection()->prepare('SELECT * FROM ' . $this->tableName . ' ORDER BY id DESC');
$stmt->execute();
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* @param $id
* @return null
*/
public function findOneById($id)
{
$stmt = $this->db->getConnection()->prepare('SELECT * FROM ' . $this->tableName . ' WHERE id = :id');
$stmt->bindParam(':id', $id, \PDO::PARAM_INT);
$stmt->execute();
$user = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$user) {
return null;
} else {
return $user;
}
}
/**
* @param $username
* @return null
*/
public function findOneByUsername($username)
{
$stmt = $this->db->getConnection()->prepare('SELECT * FROM ' . $this->tableName . ' WHERE name = :username');
$stmt->bindParam(':username', $username, \PDO::PARAM_INT);
$stmt->execute();
$user = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$user) {
return null;
} else {
return $user;
}
}
/**
* @param $email
* @return null
*/
public function findOneByEmail($email)
{
$stmt = $this->db->getConnection()->prepare('SELECT * FROM ' . $this->tableName . ' WHERE email = :email');
$stmt->bindParam(':email', $email, \PDO::PARAM_INT);
$stmt->execute();
$user = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$user) {
return null;
} else {
return $user;
}
}
/**
* @param $username
* @param $email
* @param $password
*/
public function create($username, $email, $password)
{
$stmt = $this->db->getConnection()->prepare('INSERT INTO ' . $this->tableName . ' (`name`, `email`, `password`) VALUES(:name, :email, :password)');
$stmt->bindParam(':name', $username);
$stmt->bindParam(':email', $email);
$stmt->bindParam(':password', $password);
$stmt->execute();
}
}

View File

@ -2,41 +2,51 @@
namespace App\Service;
/**
* Class Database
* @package App\Service
*/
class Database
{
private $dotEnvParser;
private $conn;
private $dsn;
private $options;
/**
* Database constructor.
*/
public function __construct()
{
$this->dotEnvParser = new DotEnvParser();
$this->dotEnvParser
->parse()
->toEnv()
->toArray();
$this->conn = null;
$dsn = "mysql:host=" . $_ENV['MYSQL_HOST'] . ";dbname=" . $_ENV['MYSQL_DBNAME'];
$options = array(
$this->dsn = "mysql:host=" . getenv('MYSQL_HOST') . ":".getenv('MYSQL_PORT').";dbname=" . getenv('MYSQL_DBNAME');
$this->options = array(
\PDO::ATTR_PERSISTENT => true,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
);
try {
$this->conn = new \PDO($dsn, $_ENV['MYSQL_USER'], $_ENV['MYSQL_PASS'], $options);
} //catch any errors
catch (\PDOException $e) {
$this->error = $e->getMessage();
}
}
/**
* Get the PDO connection instance
* @return \PDO
*/
public function getConnection()
{
if (is_null($this->conn)) {
try {
$this->conn = new \PDO($this->dsn, getenv('MYSQL_USER'), getenv('MYSQL_PASS'), $this->options);
} //catch any errors
catch (\PDOException $e) {
exit($e->getMessage());
}
}
return $this->conn;
}
/**
* gestion des erreurs de retour d'execution de PDO
* @param PDOStatement $stmt
* Handle PDO execution errors
* @param \PDOStatement $stmt
* @return void
*/
public function errorHandler(\PDOStatement $stmt) : void

View File

@ -2,42 +2,27 @@
namespace App\Service;
use josegonzalez\Dotenv\Loader;
use Codervio\Envmanager\Envparser;
/**
* Class DotEnvParser
* @package App\Service
*/
class DotEnvParser
{
private $file;
private $loader;
private $parser;
public function __construct()
{
$this->file = __DIR__ . '/../../.env';
$this->loader = new Loader($this->file);
$this->parser = new Envparser($this->file);
$this->parser->load();
}
/**
* Parse the .env file
* @return bool|Loader
*/
public function parse()
public function run()
{
return $this->loader->parse();
$this->parser->run();
}
/**
* Send the parsed .env file to the $_ENV variable
* @return bool|Loader
*/
public function toEnv()
{
return $this->loader->toEnv();
}
/**
* @return array|null
*/
public function toArray()
{
return $this->loader->toArray();
}
}
}

View File

@ -2,6 +2,10 @@
namespace App\Service;
/**
* Class JsonResponse
* @package App\Service
*/
class JsonResponse
{
public function create(int $code, string $message = null, array $data = [])
@ -12,6 +16,8 @@ class JsonResponse
'data' => $data
];
header('Access-Control-Allow-Origin: ' . getenv('ALLOW_ORIGIN'));
header('Accept: application/json');
header('Content-Type: application/json');
http_response_code($code);

View File

@ -0,0 +1,27 @@
<?php
namespace App\Service;
/**
* Class JsonResponse
* @package App\Service
*/
class Request
{
private $content;
public function getContent()
{
$this->content = trim(file_get_contents("php://input")) ?? [];
return $this;
}
public function asPlainText(){
return (string) $this->content;
}
public function jsonToArray(){
return json_decode($this->content, true);
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace App\Service;
/**
* Class Security
* @package App\Service
*/
class Security
{
/**
* @var Session
*/
private $session;
/**
* @var JsonResponse
*/
private $jsonResponse;
/**
* @var $secret_key
*/
private $secret_key;
/**
* Security constructor.
*/
public function __construct(Session $session, JsonResponse $jsonResponse)
{
$this->session = $session;
$this->jsonResponse = $jsonResponse;
$this->secret_key = getenv('APP_SECRET');
}
/**
* @return string
*/
public function generateToken($id)
{
$token = md5($id . uniqid(rand(), TRUE));
return $token;
}
/**
* @param $cookie
* @return bool
*/
public function isLogged()
{
$session = $this->session->getSession($this->getBearerToken());
$today = date("Y-m-d H:i:s");
if (is_null($session) || $session['expire_at'] < $today) {
return false;
} else {
return true;
}
}
/**
* @return string
*/
public function NotAllowedRequest()
{
$code = 403;
$message = 'You are not allowed to perform this request.';
$data = [];
return $this->jsonResponse->create($code, $message, $data);
}
/**
* @param $password
* @return bool|string
*/
public function passwordHash($password)
{
return password_hash($password, PASSWORD_BCRYPT, [
'cost' => 12
]);
}
/**
* @param $password
* @param $hash
* @return bool
*/
public function passwordVerify($password, $hash)
{
return \password_verify($password, $hash);
}
/**
* Get hearder Authorization
* */
function getAuthorizationHeader()
{
$headers = null;
if (isset($_SERVER['Authorization'])) {
$headers = trim($_SERVER["Authorization"]);
} else if (isset($_SERVER['HTTP_AUTHORIZATION'])) { //Nginx or fast CGI
$headers = trim($_SERVER["HTTP_AUTHORIZATION"]);
} elseif (function_exists('apache_request_headers')) {
$requestHeaders = apache_request_headers();
// Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
//print_r($requestHeaders);
if (isset($requestHeaders['Authorization'])) {
$headers = trim($requestHeaders['Authorization']);
}
}
return $headers;
}
/**
* Get access token from header
*/
function getBearerToken()
{
$headers = $this->getAuthorizationHeader();
// HEADER: Get the access token from the header
if (!empty($headers)) {
if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
return $matches[1];
}
}
return null;
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Service;
use App\Repository\SessionRepository;
/**
* Class Session
* @package App\Service
*/
class Session
{
private $db;
private $sessionRepository;
public $security;
/**
* Session constructor.
*/
public function __construct(Database $database, JsonResponse $jsonResponse)
{
$this->db = $database;
$this->security = new Security($this, $jsonResponse);
$this->sessionRepository = new SessionRepository($this->db, $this->security);
}
/**
* @param $cookie
* @return mixed|null
*/
public function getSession($token)
{
$stmt = $this->db->getConnection()->prepare('SELECT * FROM Session WHERE token = :token');
$stmt->bindParam(':token', $token);
$stmt->execute();
$session = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$session) {
return null;
} else {
return $session;
}
}
/**
* @return array
*/
public function getUser()
{
return $this->sessionRepository->findUserBySessionToken($this->security->getBearerToken());
}
}