diff --git a/Groupe/GroupeRepository.php b/Groupe/GroupeRepository.php index 1abb8039..c20a616b 100644 --- a/Groupe/GroupeRepository.php +++ b/Groupe/GroupeRepository.php @@ -10,7 +10,7 @@ * @author Wouldsmina * * @since 0.7 - * @see \LibertAPI\Tests\Units\Planning\PlanningRepository + * @see \LibertAPI\Tests\Units\Groupe\GroupeRepository * * Ne devrait être contacté que par le GroupeController * Ne devrait contacter que le GroupeEntite diff --git a/Heure/Repos/ReposEntite.php b/Heure/Repos/ReposEntite.php new file mode 100644 index 00000000..83a33d11 --- /dev/null +++ b/Heure/Repos/ReposEntite.php @@ -0,0 +1,90 @@ + + * @author Wouldsmina + * + * @since 1.8 + * @see \LibertAPI\Tests\Units\Heure\Repos\ReposEntite + * + * Ne devrait être contacté que par le ReposRepository + * Ne devrait contacter personne + */ +class ReposEntite extends \LibertAPI\Tools\Libraries\AEntite +{ + /** + * Retourne la donnée la plus à jour du champ login + */ + public function getLogin() : string + { + return $this->getFreshData('login'); + } + + /** + * Retourne la donnée la plus à jour du champ debut + */ + public function getDebut() : int + { + return $this->getFreshData('debut'); + } + + /** + * Retourne la donnée la plus à jour du champ fin + */ + public function getFin() : int + { + return $this->getFreshData('fin'); + } + + /** + * Retourne la donnée la plus à jour du champ duree + */ + public function getDuree() : int + { + return $this->getFreshData('duree'); + } + + /** + * Retourne la donnée la plus à jour du champ type_periode + */ + public function getTypePeriode() : int + { + return $this->getFreshData('type_periode'); + } + + /** + * Retourne la donnée la plus à jour du champ statut + */ + public function getStatut() : int + { + return $this->getFreshData('statut'); + } + + /** + * Retourne la donnée la plus à jour du champ commentaire + */ + public function getCommentaire() : string + { + return $this->getFreshData('commentaire'); + } + + /** + * Retourne la donnée la plus à jour du champ commentaire_refus + */ + public function getCommentaireRefus() : string + { + return $this->getFreshData('commentaire_refus'); + } + + /** + * @inheritDoc + */ + public function populate(array $data) + { + } +} diff --git a/Heure/Repos/ReposRepository.php b/Heure/Repos/ReposRepository.php new file mode 100644 index 00000000..3a5b8e28 --- /dev/null +++ b/Heure/Repos/ReposRepository.php @@ -0,0 +1,96 @@ + + * @author Wouldsmina + * + * @since 1.8 + * @see \LibertAPI\Tests\Units\Heure\Repos\ReposRepository + * + * Ne devrait être contacté que par le HeureReposEmployeController + * Ne devrait contacter que le ReposEntite + */ +class ReposRepository extends \LibertAPI\Tools\Libraries\ARepository +{ + final protected function getEntiteClass() : string + { + return ReposEntite::class; + } + + /** + * @inheritDoc + */ + final protected function getParamsConsumer2Storage(array $paramsConsumer) : array + { + $results = []; + if (array_key_exists('login', $paramsConsumer)) { + $results['login'] = (string) $paramsConsumer['login']; + } + + return $results; + } + + /** + * @inheritDoc + */ + final protected function getStorage2Entite(array $dataStorage) + { + return [ + 'id' => $dataStorage['id_heure'], + 'login' => $dataStorage['login'], + 'debut' => (int) $dataStorage['debut'], + 'fin' => (int) $dataStorage['fin'], + 'duree' => (int) $dataStorage['duree'], + 'type_periode' => (int) $dataStorage['type_periode'], + 'statut' => (int) $dataStorage['statut'], + 'commentaire' => $dataStorage['comment'], + 'commentaire_refus' => $dataStorage['comment_refus'], + ]; + } + + /** + * @inheritDoc + */ + final protected function setValues(array $values) + { + unset($values); + } + + final protected function setSet(array $parametres) + { + unset($parametres); + } + + /** + * @inheritDoc + */ + final protected function setWhere(array $parametres) + { + if (array_key_exists('login', $parametres)) { + $this->queryBuilder->andWhere('login = :login'); + $this->queryBuilder->setParameter(':login', $parametres['login']); + } + } + + /** + * @inheritDoc + */ + final protected function getEntite2Storage(AEntite $entite) : array + { + unset($entite); + return []; + } + + /** + * @inheritDoc + */ + final protected function getTableName() : string + { + return 'heure_repos'; + } +} diff --git a/Makefile b/Makefile index e7fc9b98..0fc51759 100644 --- a/Makefile +++ b/Makefile @@ -11,28 +11,36 @@ define make_version @git tag -a `semver tag` -m "Releasing `semver tag`" endef -default : help +.DEFAULT_GOAL := help + +# +# Thanks to https://blog.theodo.fr/2018/05/why-you-need-a-makefile-on-your-project/ +# help: - @echo 'help' + @grep -E '(^[a-zA-Z_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}{printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/' + -install: +## Installation +install: ## Installe les dépendances composer php composer.phar install -major: +## Administration +major: ## Monte la version majeure du logiciel $(call make_version,major) -minor: +minor: ## Monte la version mineure du logiciel $(call make_version,minor) -patch: +patch: ## Monte la version patch du logiciel $(call make_version,patch) -test: test-unit test-functional +## CI +test: test-unit test-functional ## Lance tous les tests applicatifs test-unit: ## Lance les tests unitaires Vendor/Bin/atoum -ulr -test-functional: +test-functional: ## Lance les tests fonctionnels cp Tests/Functionals/_data/database.sqlite Tests/Functionals/_data/current.sqlite Vendor/Bin/codecept run api -f diff --git a/Tests/Units/Heure/Repos/ReposEntite.php b/Tests/Units/Heure/Repos/ReposEntite.php new file mode 100644 index 00000000..2f394d22 --- /dev/null +++ b/Tests/Units/Heure/Repos/ReposEntite.php @@ -0,0 +1,67 @@ + + * @author Wouldsmina + * + * @since 1.8 + */ +final class ReposEntite extends \LibertAPI\Tests\Units\Tools\Libraries\AEntite +{ + /** + * @inheritDoc + */ + public function testConstructWithId() + { + $id = 58; + $commentaire = 'Barry Allen'; + $commentaireRefus = 'Barry Allen 2'; + + $this->newTestedInstance([ + 'id' => $id, + 'login' => 'Abagnale', + 'debut' => 1000, + 'fin' => 1100, + 'duree' => 10, + 'type_periode' => 7, + 'statut' => 10, + 'commentaire' => $commentaire, + 'commentaire_refus' => $commentaireRefus, + ]); + + $this->assertConstructWithId($this->testedInstance, $id); + $this->string($this->testedInstance->getLogin())->isIdenticalTo('Abagnale'); + $this->integer($this->testedInstance->getDebut())->isIdenticalTo(1000); + $this->integer($this->testedInstance->getFin())->isIdenticalTo(1100); + $this->integer($this->testedInstance->getDuree())->isIdenticalTo(10); + $this->integer($this->testedInstance->getTypePeriode())->isIdenticalTo(7); + $this->integer($this->testedInstance->getStatut())->isIdenticalTo(10); + $this->string($this->testedInstance->getCommentaire())->isIdenticalTo($commentaire); + $this->string($this->testedInstance->getCommentaireRefus())->isIdenticalTo($commentaireRefus); + } + + /** + * @inheritDoc + */ + public function testConstructWithoutId() + { + $this->newTestedInstance([ + 'login' => 'Abagnale', + 'debut' => 1000, + ]); + $this->variable($this->testedInstance->getId())->isNull(); + } + + /** + * @inheritDoc + */ + public function testReset() + { + $this->newTestedInstance(['login' => 'Abagnale', 'debut' => 1000,]); + + $this->assertReset($this->testedInstance); + } +} diff --git a/Tests/Units/Heure/Repos/ReposRepository.php b/Tests/Units/Heure/Repos/ReposRepository.php new file mode 100644 index 00000000..f3509402 --- /dev/null +++ b/Tests/Units/Heure/Repos/ReposRepository.php @@ -0,0 +1,37 @@ + + * @author Wouldsmina + * + * @since 1.8 + */ +final class ReposRepository extends \LibertAPI\Tests\Units\Tools\Libraries\ARepository +{ + final protected function getStorageContent() : array + { + return [ + 'id_heure' => 42, + 'login' => 'Sherlock', + 'debut' => 7427, + 'fin' => 4527, + 'duree' => 78, + 'type_periode' => 26, + 'statut' => 3, + 'comment' => 'Arsène', + 'comment_refus' => 'Lupin', + ]; + } + + protected function getConsumerContent() : array + { + return [ + 'login' => 'Watson', + 'debut' => 77, + 'fin' => 89432, + ]; + } +} diff --git a/Tools/App.php b/Tools/App.php index 66444f90..fc746bf3 100644 --- a/Tools/App.php +++ b/Tools/App.php @@ -29,9 +29,10 @@ return $response->withJson('Hi there !'); }); -require_once ROUTE_PATH . DS. 'Absence.php'; +require_once ROUTE_PATH . DS . 'Absence.php'; require_once ROUTE_PATH . DS . 'Authentification.php'; require_once ROUTE_PATH . DS . 'Groupe.php'; +require_once ROUTE_PATH . DS . 'Heure.php'; require_once ROUTE_PATH . DS . 'Journal.php'; require_once ROUTE_PATH . DS . 'JourFerie.php'; require_once ROUTE_PATH . DS . 'Planning.php'; diff --git a/Tools/Controllers/HeureReposEmployeController.php b/Tools/Controllers/HeureReposEmployeController.php new file mode 100644 index 00000000..1fb9978d --- /dev/null +++ b/Tools/Controllers/HeureReposEmployeController.php @@ -0,0 +1,71 @@ + + * @author Wouldsmina + * + * @since 1.8 + */ +final class HeureReposEmployeController extends \LibertAPI\Tools\Libraries\AController +implements Interfaces\IGetable +{ + public function __construct(Repos\ReposRepository $repository, IRouter $router) + { + parent::__construct($repository, $router); + } + + /** + * {@inheritDoc} + */ + public function get(IRequest $request, IResponse $response, array $arguments) : IResponse + { + unset($arguments); + return $this->getList($request, $response); + } + + /** + * Retourne un tableau d'heures de repos + */ + private function getList(IRequest $request, IResponse $response) : IResponse + { + $user = $request->getAttribute('currentUser'); + $arguments = array_merge($request->getQueryParams(), ['login' => $user->getLogin()]); + try { + $responseResources = $this->repository->getList($arguments); + } catch (\UnexpectedValueException $e) { + return $this->getResponseNoContent($response); + } catch (\Exception $e) { + return $this->getResponseError($response, $e); + } + $entites = array_map([$this, 'buildData'], $responseResources); + + return $this->getResponseSuccess($response, $entites, 200); + } + + /** + * Construit le « data » du json + */ + private function buildData(Repos\ReposEntite $entite) : array + { + return [ + 'id' => $entite->getId(), + 'login' => $entite->getLogin(), + 'debut' => $entite->getDebut(), + 'fin' => $entite->getFin(), + 'duree' => $entite->getDuree(), + 'type_periode' => $entite->getTypePeriode(), + 'statut' => $entite->getStatut(), + 'commentaire' => $entite->getCommentaire(), + 'commentaire_refus' => $entite->getCommentaireRefus(), + ]; + } +} diff --git a/Tools/Middlewares/AccessChecker.php b/Tools/Middlewares/AccessChecker.php index f7dcdd94..e3d7e47b 100644 --- a/Tools/Middlewares/AccessChecker.php +++ b/Tools/Middlewares/AccessChecker.php @@ -18,20 +18,20 @@ public function __invoke(IRequest $request, IResponse $response, callable $next) $container = $this->getContainer(); switch ($ressourcePath) { - case 'Absence|Type': case 'Absence|Periode': - case 'Utilisateur': - case 'JourFerie': - case 'Journal': + case 'Absence|Type': case 'Authentification': + case 'Employe|Me|Heure|Repos': case 'HelloWorld': + case 'Journal': case 'Planning|Creneau': + case 'Utilisateur': return $next($request, $response); case 'Groupe': + case 'Groupe|Employe': case 'Groupe|GrandResponsable': case 'Groupe|Responsable': - case 'Groupe|Employe': - $user = $container->get('currentUser'); + $user = $request->getAttribute('currentUser'); if (!$user->isAdmin()) { return call_user_func( $container->get('forbiddenHandler'), @@ -42,7 +42,7 @@ public function __invoke(IRequest $request, IResponse $response, callable $next) return $next($request, $response); case 'JourFerie': - $user = $container->get('currentUser'); + $user = $request->getAttribute('currentUser'); if (!$user->isHautResponsable()) { return call_user_func( $container->get('forbiddenHandler'), @@ -53,7 +53,7 @@ public function __invoke(IRequest $request, IResponse $response, callable $next) return $next($request, $response); case 'Planning': - $user = $container->get('currentUser'); + $user = $request->getAttribute('currentUser'); if (!$user->isResponsable() && !$user->isHautResponsable() && !$user->isAdmin()) { return call_user_func( $container->get('forbiddenHandler'), diff --git a/Tools/Middlewares/Identificator.php b/Tools/Middlewares/Identificator.php index f93394f4..b9831f52 100644 --- a/Tools/Middlewares/Identificator.php +++ b/Tools/Middlewares/Identificator.php @@ -26,8 +26,8 @@ public function __invoke(IRequest $request, IResponse $response, callable $next) } elseif ($this->isIdentificationOK($request, $repoUtilisateur)) { // Ping de last_access $utilisateur = $repoUtilisateur->updateDateLastAccess($this->utilisateur); + $request = $request->withAttribute('currentUser', $utilisateur); - $container->set('currentUser', $utilisateur); return $next($request, $response); } diff --git a/Tools/Route/Absence.php b/Tools/Route/Absence.php index 54e28fdf..ff5c52b8 100644 --- a/Tools/Route/Absence.php +++ b/Tools/Route/Absence.php @@ -30,6 +30,5 @@ $this->get('/{periodeId:[0-9]+}', [AbsencePeriodeController::class, 'get'])->setName('getAbsencePeriodeDetail'); /* Collection */ $this->get('', [AbsencePeriodeController::class, 'get'])->setName('getAbsencePeriodeListe'); - }); }); diff --git a/Tools/Route/Heure.php b/Tools/Route/Heure.php new file mode 100644 index 00000000..6f3808ee --- /dev/null +++ b/Tools/Route/Heure.php @@ -0,0 +1,14 @@ +get('/employe/me/heure/repos', [HeureReposEmployeController::class, 'get'])->setName('getHeureReposEmployeMeListe'); diff --git a/decisions.md b/decisions.md index 8172bf38..0ad234f6 100644 --- a/decisions.md +++ b/decisions.md @@ -1,15 +1,23 @@ +# 2019-04-06 +* Il me paraît naturel sémantiquement d'avoir l'utilisateur courant dans la request, je l'y place donc. +* Nous commençons la création des routes `employe/me/[ressources]` qui fournit des opérations automatiquement filtrées sur l'utilisateur courant. La convention est de décrire ces routes dans le fichier dédié à la ressource (ie. `employe/me/heure/repos` dans `/Tools/Route/Heure.php`). + +~ Prytoegrian + # 2018-12-17 -* Afin de mieux coller aux résultats de `__DIR__` et `dirname()`, je supprime toutes les slashes finaux des constantes `*_PATH` +* Afin de mieux coller aux résultats de `__DIR__` et `dirname()`, je supprime tous les slashes finaux des constantes `*_PATH` + +~ Prytoegrian # 2018-10-20 * Il y a de multiples méthodes de connexion à l'application « libertempo » et l'API commence à les absorber petit à petit. Naturellement, j'ai souhaité que cette diversité de connecteurs soit transparente pour la plus grande partie de l'appli possible. J'ai donc mis en place une Fabrique pour que cette dernière fasse seule le choix du connecteur à sélectionner, ses consommateurs manipulant un contrat. -* À cet effet, le contrôleur d'authentification n'est plus testable unitairement (le statiqu l'en empêche). Je souhaiterais ne pas rester sur un échec et tester cette partie d'une autre manière. -* Pour conserver la séparation stricte entre newable et injectable, j'ai de plus muté la Request serveur pour qu'elle contienne la configuration LDAP ; je ne suis pas trop fan de la solution et aimerait trouver une approche plus élégante et pérenne. +* À cet effet, le contrôleur d'authentification n'est plus testable unitairement (le static l'en empêche). Je souhaiterais ne pas rester sur un échec et tester cette partie d'une autre manière. +* Pour conserver la séparation stricte entre newable et injectable, j'ai de plus muté la Request serveur pour qu'elle contienne la configuration LDAP ; je ne suis pas trop fan de la solution et aimerais trouver une approche plus élégante et pérenne. ~ Prytoegrian ## 2018-06-07 -* J'ai désormais appliqué le paradigme « package by components » attendu que le « package by feature » soulevait des embûches. En effet, le contrôleur ne fait pas parti d'un composant, il est un moyen d'accès vers lui ; je l'ai donc déplacé dans `Tools`. À l'exception des répertoires d'utilitaires, nous aurons ainsi une vision claire des objectifs du logiciel du premier coup d'œil et une place toute trouvée pour les structures applicatives relatives au différents métiers du soft. +* J'ai désormais appliqué le paradigme « package by components » attendu que le « package by feature » soulevait des embûches. En effet, le contrôleur ne fait pas partie d'un composant, il est un moyen d'accès vers lui ; je l'ai donc déplacé dans `Tools`. À l'exception des répertoires d'utilitaires, nous aurons ainsi une vision claire des objectifs du logiciel du premier coup d'œil et une place toute trouvée pour les structures applicatives relatives au différents métiers du soft. * Un framework d'injection de dépendances a également fait son entrée. Il nous aidera à supprimer tout un pan de verbosité, facilite le test et nous permettra d'exprimer le plus précisément possible les interactions entre les structures applicatives. * Le problème des relations N-N n'en est pas encore un. Pour le moment, j'ai créé des entités pour les représentants de ces associations que l'on utilisera côté client. Si besoin il y a, nous créerons des Aggrégats.