# API Projects # Sanazoo [GitHub repo](https://github.com/Nouuu/SanaZoo-API) ![package.json version](https://img.shields.io/github/package-json/v/SwannHERRERA/SanaZoo-API)![express version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/express)![sequelize version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/sequelize)![typescript version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/dev/typescript) SanaZoo is a very popular zoo ! First created in C with XML files, it is now developed with nodejs and swagger, for your eyes only ## Our project In this school project, we have to realize a complete API to manage a zoo, using Express and Sequelize as a base. This project has been tested and integrated both on heroku, but also thanks to docker whose image is detailed below Project Syllabus : [Syllabus.pdf](https://wiki2.nospy.fr/attachments/1) ### Gantt chart This project was carried out using a gantt chart : [![image-20210424163444370.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20210424163444370.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20210424163444370.png) ### Data model used for DB Here is our DB model used for this project : [![Planode-Zoo.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/planode-zoo.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/planode-zoo.png) ### Contributions
[Noé LARRIEU-LACOSTE](https://github.com/Nouuu)[![followers](https://img.shields.io/github/followers/nouuu)]((https://github.com/Nouuu))
[Swann HERRERA](https://github.com/SwannHERRERA)[![followers](https://img.shields.io/github/followers/SwannHERRERA)](https://github.com/SwannHERRERA)
[Clément BOSSARD](https://github.com/Huriumari)[![followers](https://img.shields.io/github/followers/Huriumari)](https://github.com/Huriumari)
## Information about code ### Docker integration Our docker image is built in 2 step : - First we build all the project with dev dependencies - Then we only keep production dependencies with compiled project This reduce drastictly the size of the image ```dockerfile ## Stage 1 building the code FROM node:lts-alpine as builder WORKDIR /usr/app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build ## Stage 2 final stage with builded code FROM node:lts-alpine WORKDIR /usr/app COPY package*.json ./ RUN npm ci --production COPY --from=builder /usr/app/dist ./dist ENV PORT=3000 \ DB_PORT=3306 \ DB_DRIVER='mysql' \ DB_HOST='localhost' \ DB_NAME='zoo' \ DB_USER='root' \ DB_PASSWORD='' CMD node dist/src/index.js ``` #### Env
Environment variableDefaultDescription
PORT3000Express listen port
DB\_DRIVERmysqlDriver for sql connection for sequelize
DB\_HOSTlocalhostHost domain / IP for DB
DB\_NAMEzooDB Schema name
DB\_USERzooDB user
DB\_PASSWORD`empty`DB password
### Main dependencies
DependencyVersionDescription
Express![express version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/express)Web API Framework
Date FNS![date-fns version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/date-fns)Useful librairies to manipulates dates
Dotenv![date fns version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/dotenv)Used to load `.env` file
Argon2![argon2 version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/argon2)Used to encrupt users password
Mysql2![mysql version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/mysql2)DB driver
Sequelize![sequelize version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/sequelize)Orm librairies to bind class to DB entities
Swagger-jsdoc![version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/swagger-jsdoc)Used to implements swagger page
Swagger-ui-express![version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/swagger-ui-express)Used to implements swagger page
Yup![yup version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/yup)Form validation library used to validate data in post body of our requests
Typescript![typescript version](https://img.shields.io/github/package-json/dependency-version/SwannHERRERA/SanaZoo-API/dev/typescript)Very useful to use types in JS based framework
## API Endpoints ### Postman Environment You can check our endpoints with postman directly on this URL: [https://documenter.getpostman.com/view/11568150/TzJvdwNA](https://documenter.getpostman.com/view/11568150/TzJvdwNA) **API Description :** - [Affluence](#affluence) - [Daily](#1-daily) - [Daily by enclosure](#2-daily-by-enclosure) - [Live enclosure affluence](#3-live-enclosure-affluence) - [Monthly](#4-monthly) - [Monthly by enclosure](#5-monthly-by-enclosure) - [Total](#6-total) - [Total by enclosure](#7-total-by-enclosure) - [Weekly](#8-weekly) - [Weekly by enclosure](#9-weekly-by-enclosure) - [1Yearly](#10-yearly) - [1Yearly by enclosure](#11-yearly-by-enclosure) - [Animal](#animal) - [Create](#1-create) - [Delete](#2-delete) - [Get all](#3-get-all) - [Get by id](#4-get-by-id) - [Move Enclosure](#5-move-enclosure) - [Update](#6-update) - [Animal Health Book](#animal-health-book) - [Create entry](#1-create-entry) - [Delete](#2-delete-1) - [Get All](#3-get-all) - [Get All By Animal](#4-get-all-by-animal) - [Get One](#5-get-one) - [Update](#6-update-1) - [Enclosure](#enclosure) - [Add One](#1-add-one) - [Delete One](#2-delete-one) - [Edit One](#3-edit-one) - [Get All](#4-get-all) - [Get All Animals In Enclosure](#5-get-all-animals-in-enclosure) - [Get All By Type](#6-get-all-by-type) - [Get One](#7-get-one) - [Enclosure Images](#enclosure-images) - [Add](#1-add) - [Delete One](#2-delete-one-1) - [Edit One](#3-edit-one-1) - [Gell All From Enclosure](#4-gell-all-from-enclosure) - [Get All](#5-get-all) - [Get One](#6-get-one) - [Enclosure Service-book](#enclosure-service-book) - [Create service-book](#1-create-service-book) - [Delete service-book](#2-delete-service-book) - [Edit service-book](#3-edit-service-book) - [Gell All From Employee](#4-gell-all-from-employee) - [Get All](#5-get-all-1) - [Get All From Enclosure](#6-get-all-from-enclosure) - [Get One](#7-get-one-1) - [Enclosure Type](#enclosure-type) - [Create](#1-create-1) - [Delete](#2-delete-2) - [Get All](#3-get-all-1) - [Get One](#4-get-one) - [Update](#5-update) - [Entry](#entry) - [Add entry](#1-add-entry) - [Get all](#2-get-all) - [Get by enclosure](#3-get-by-enclosure) - [Get by pass](#4-get-by-pass) - [Get by user](#5-get-by-user) - [Remove entry](#6-remove-entry) - [Maintenance](#maintenance) - [Get All by State](#1-get-all-by-state) - [Get Best Month](#2-get-best-month) - [Update Maintenance State](#3-update-maintenance-state) - [Pass](#pass) - [Add enclosure access](#1-add-enclosure-access) - [Create](#2-create) - [Delete pass](#3-delete-pass) - [Get all](#4-get-all) - [Get all by user id](#5-get-all-by-user-id) - [Get by id](#6-get-by-id) - [Remove enclosure access](#7-remove-enclosure-access) - [Update pass](#8-update-pass) - [Pass Night](#pass-night) - [Add availability](#1-add-availability) - [Delete passnight](#2-delete-passnight) - [Get all](#3-get-all-1) - [Get all valid](#4-get-all-valid) - [Update passnight](#5-update--passnight) - [Pass Type](#pass-type) - [Add pass to pass type](#1-add-pass-to-pass-type) - [Create](#2-create-1) - [Delete](#3-delete) - [Get all](#4-get-all-1) - [Get by id](#5-get-by-id) - [Update](#6-update-2) - [Planning](#planning) - [Add](#1-add-1) - [Delete](#2-delete-3) - [Get All](#3-get-all-2) - [Get Calendar](#4-get-calendar) - [Get One](#5-get-one-1) - [Get Open Date](#6-get-open-date) - [Update](#7-update) - [Specie](#specie) - [Create](#1-create-2) - [Delete](#2-delete-4) - [Get All](#3-get-all-3) - [Get By ID](#4-get-by-id) - [Update](#5-update-1) - [Statistics](#statistics) - [Count all pass](#1-count-all-pass) - [Count all pass by types](#2-count-all-pass-by-types) - [Count animal](#3-count-animal) - [Count animal by enclosure](#4-count-animal-by-enclosure) - [Count enclosure](#5-count-enclosure) - [Count expired pass](#6-count-expired-pass) - [Count user](#7-count-user) - [Count valid pass](#8-count-valid-pass) - [Count valid pass by types](#9-count-valid-pass-by-types) - [User](#user) - [Change Password](#1-change-password) - [Create](#2-create-2) - [Delete](#3-delete-1) - [Force Delete](#4-force-delete) - [Get All](#5-get-all-2) - [Get By ID](#6-get-by-id) - [Login](#7-login) - [Logout](#8-logout) - [ME](#9-me) - [Register](#10-register) - [Restaure User](#11-restaure-user) - [Update by admin](#12-update-by-admin) - [Update client only by employee](#13-update-client-only-by-employee) - [User Role](#user-role) - [Affect User](#1-affect-user) - [Create](#2-create-3) - [Delete](#3-delete-2) - [Get All](#4-get-all-1) - [Get by ID](#5-get-by-id) - [Update](#6-update-3) - [Swagger](#swagger) --- ### Swagger This project contain a complete swagger test environment to use API, you can access it on `https://domain.example/swagger` It look FABULOUS : [![image-20210424170307083.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20210424170307083.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20210424170307083.png) # Trade Me [GitHub repo](https://github.com/Nouuu/AL-TradeMe)
[![github](https://img.shields.io/badge/repository-github-blue)](https://github.com/Nouuu/AL-TradeMe)
Main status[![Coverage](https://sonar.nospy.fr/api/project_badges/measure?branch=main&project=Nouuu_AL-TradeMe&metric=coverage&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe)[![Maintainability Rating](https://sonar.nospy.fr/api/project_badges/measure?branch=main&project=Nouuu_AL-TradeMe&metric=sqale_rating&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=main)[![Quality Gate Status](https://sonar.nospy.fr/api/project_badges/measure?branch=main&project=Nouuu_AL-TradeMe&metric=alert_status&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=main)[![Reliability Rating](https://sonar.nospy.fr/api/project_badges/measure?branch=main&project=Nouuu_AL-TradeMe&metric=reliability_rating&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=main)[![Security Rating](https://sonar.nospy.fr/api/project_badges/measure?branch=main&project=Nouuu_AL-TradeMe&metric=security_rating&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=main)
[![Code smells](https://sonar.nospy.fr/api/project_badges/measure?branch=main&project=Nouuu_AL-TradeMe&metric=code_smells&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=main) [![Bugs](https://sonar.nospy.fr/api/project_badges/measure?branch=main&project=Nouuu_AL-TradeMe&metric=bugs&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=main)[![Vulnerabilities](https://sonar.nospy.fr/api/project_badges/measure?branch=main&project=Nouuu_AL-TradeMe&metric=vulnerabilities&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=main)
[![Technical Debt](https://sonar.nospy.fr/api/project_badges/measure?branch=main&project=Nouuu_AL-TradeMe&metric=sqale_index&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=main)
Dev status[![Coverage](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_AL-TradeMe&metric=coverage&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=dev)[![Maintainability Rating](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_AL-TradeMe&metric=sqale_rating&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=dev)[![Quality Gate Status](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_AL-TradeMe&metric=alert_status&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=dev)[![Reliability Rating](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_AL-TradeMe&metric=reliability_rating&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=dev)[![Security Rating](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_AL-TradeMe&metric=security_rating&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=dev)
[![Code smells](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_AL-TradeMe&metric=code_smells&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=dev) [![Bugs](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_AL-TradeMe&metric=bugs&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=dev)[![Vulnerabilities](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_AL-TradeMe&metric=vulnerabilities&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=dev)
[![Technical Debt](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_AL-TradeMe&metric=sqale_index&token=edc93fd166b059d5befe7e2fe22d2e0d10d9b853)](https://sonar.nospy.fr/dashboard?id=Nouuu_AL-TradeMe&branch=dev)
## Fonctionnalités métiers ### Membres - Ajouter / Modifier / Supprimer un **Contractor** - Ajouter / Modifier / Supprimer un **Tradesman** - Faire une demande de paiement pour un **Contractor** - Faire une demande de paiement pour un **Tradesman** - Libérer un **Tradesman** d'un **Projet** - Assigner un **Tradesman** à un **Projet** - Mettre à jour les attributs professionnel d'un **Tradesman** - Mettre à jour le statut de paiement d'un **Tradesman** - Mettre à jour le statut de paiement d'un **Contractor** - Trouver un **Tradesman** qui match pour un **Projet** - Récupérer un **Tradesman** - Récupérer tout les **Tradesman** - Récupérer les compétences d'un **Tradesman** - Récupérer un **Contractor** - Récupérer tout les **Contractor** ### Paiement - Payer un abonnement pour un **Contractor** - Payer un abonnement pour un **Tradesman** ### Factures - Créer une **facture** - Supprimer les **factures** d'un **Contractor** - Supprimer les **factures** d'un **Tradesman** - Récupérer toutes les **factures** - Récupérer les **factures** des **Tradesman** - Récupérer les **factures** des **Contractor** - Récupérer les **factures** d'un **Tradesman** - Récupérer les **factures** d'un **Contractor** - Récupérer une **facture** ### Projets - Ajouter / Modifier un **Projet** - Terminer un **Projet** - Ajouter / Retirer un métier au **Projet** - Ajouter / Modifier / Retirer une compétence requise à un **Projet** - Assigner un **Tradesman** à un **Projet** - Enlever un **Tradesman** du **Projet** - Récupérer les **Projets** - Récupérer un **Projet** - Récupérer les **Projets** d'un **Contractor** - Récupérer les **Projets** d'un **Tradesman** - Récupérer les compétences requises d'un **Projet** ## Architecture choisie ### Onion Architecture Pour mener a bien le projet l'on s'est inspiré de concept qui viennent des architectures dites **clean** et de garder ces 3 objectifs en tête. 1. Être independent du framework et des librairies externe. 2. Testable : Il doit être facile d'ajouté des tests dans la base de code. 3. Être independent de la manière dont on fait persister nos données. ### Domain-Driven Design Lors de la conception du projet on a essayé d'utiliser une approche pilotée par les usecase et le Domaine, en suivant ce que l'on connaissait du **DDD** (**Domain-Driven Design**) et fonctionnant avec des aggregates. En utilisant ubiquitous langage, pour partager un langage comment au sein de l'équipe, qui est le langage du métier. On a bien séparé nos features dans des **bounded context** qui partage ce qu'ils ont en commun (les **events** et certain **model interne**) via le **shared kernel**. ### Staged event-driven architecture L'**architecture événementielle par étapes** (**SEDA**) fait référence à une approche de l'architecture logicielle qui décompose le cycle de vie d'un processus en un ensemble d'étapes reliées par des files d'attente. Il évite la surcharge élevée associée aux threads basés sur les modèles de concurrence et découple la planification des événements et des threads de la logique de l'application. Chaque fonctionnalité de l'application est gérée par un bus d'évènement principal, qui permet de relier ces évènements à des observateurs présents dans une ou plusieurs autres fonctionnalités afin de pouvoir exécuter certaines actions secondaire. De cette manière, nous pouvons gérer grâce à un maillage entre événement et observateurs tout le comportement de nos fonctionnalités entre elles sans que ces dernières ne communiquent jamais directement entre elle. Un des nombreux avantages que cela représente et le découpage de notre application qui devient beaucoup plus simple, et qui se prête naturellement aux **micro services**. ### Architectural Decision Record Voilà quelques décisions d'architecture que nous avons pris pendant le développement du projet : - [Communications externes](https://wiki2.nospy.fr/attachments/3) - [Communications internes](https://wiki2.nospy.fr/attachments/2) - [Service VS Command/Query Handler](https://wiki2.nospy.fr/attachments/4) - [Utilisation des records](https://wiki2.nospy.fr/attachments/5) - [Validation](https://wiki2.nospy.fr/attachments/6) ## Implémentation ### Dependency Inversion Principle Le principe d'inversion des dépendances correspond au « **D** » de l'acronyme **SOLID**. En suivant ce principe, la relation de dépendance conventionnelle que les modules de haut niveau ont, par rapport aux modules de bas niveau, est inversée dans le but de rendre les premiers indépendants des seconds. Les deux assertions de ce principe sont : 1. Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d'abstractions. 2. Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions. Ce principe a été respecté pour cette application. ### Command Query Separation La **séparation commande-requête** est un principe de la programmation impérative. Elle stipule que chaque méthode doit être une ***commande*** qui effectue une action ou une ***requête*** qui renvoie des données à l'appelant, mais pas les deux. Plus formellement, les méthodes ne devraient retourner une valeur que si elles sont référentiellement transparentes et ne possèdent donc pas d'effets de bord. Ce principe a été respecté au maximum au sein de l'application, même les observateurs d'événements utilisent ce principe. ### Packages #### Application Le package applicatif contient le **traitement dit métier** de notre application. Ce sont eux qui vont utiliser les différentes ressources de notre application pour **exécuter les traitements de leurs propres domaines** Les services présents dans ce package se basent principalement sur les **interfaces** de nos autres classes afin de ne pas être dépendants d'une implémentation en particulier. On peut faire ça grâce au **polymorphisme**, la **programmation par interfaces** et le **pattern dependency injection**. Ces mêmes services sont des "micro-services" qui respecte le fameux pattern **CQS** et sont donc des **Query** / **Command** handlers. Cas d'utilisation, création d'un contractor : [![CreateContractorService_handle.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/createcontractorservice-handle.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/createcontractorservice-handle.png) #### API Ce package est utilisé pour pouvoir avoir recours à des services externes en utilisant le **pattern strategy**. L'interface est présente dans le package **api** et son implémentation dans **l'infrastructure**. Pour le moment, une seule API est présente, celle du **paiement** qui peut potentiellement lancer une exception ou pas pour indiquer si la transaction s'est bien effectué. Elle est actuellement implémentée par un **stub** qui ne déclenche pas d'exception (paiement effectué). #### Configuration Ce package est celui qui permet le maillage entre toutes nos interfaces et leurs implémentations, c'est lui qui va gérer le contexte et l'injection de dépendances. #### Domain Ce package contient tous les modèles du domaine métier de notre application. C'est également celui qui contient les différentes interfaces qui peuvent être injectés dans nos services applicatifs (ex : Les interfaces des repositories). ##### Event Afin de pouvoir prendre en compte **différents traitements**, sans avoir à modifier le service et que ce dernier n'ai qu'**une seule responsabilité**, on utilisera le **pattern event, observable** afin de lancer un événement lors de différentes actions menés à l'intérieur d'un service. Ces mêmes événements seront alors pris en charge par des observables (listener) un peu partout dans le programme, qui feront eux même appel à une command / requête pour déclencher une action secondaire au traitement initial. Les différentes tâches à exécuter suite à cet enregistrement n'auront alors qu'à **s'inscrire à cet événement** pour lancer leur propre traitement. Cas d'utilisation, création d'une facture suite à un paiement effectué : [![ContractorSubscriptionPaymentService_handle.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/contractorsubscriptionpaymentservice-handle.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/contractorsubscriptionpaymentservice-handle.png) [![NewContractorSubscriptionPaymentListener_accept.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/newcontractorsubscriptionpaymentlistener-accept.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/newcontractorsubscriptionpaymentlistener-accept.png) ##### Model Ce package contient toutes les entités utilisées dans l'application, elles suivent le **pattern value object** ainsi qu'**entity**. L'objet est donc immutable et possède un identifiant pour son utilisation à travers un repository. ##### Exception Contient les exceptions d'exécution du domaine métiers tel que **PaymentException** si le paiement a échoué, \*\* UserInvalidException\*\* si l'utilisateur n'est pas valide et **UserNotFoundException** si l'utilisateur n'est pas présent dans le repository implémenté. #### Features Chaque fonctionnalité de notre application est séparée dans différents packages à l'intérieur du package feature. Ces dernières ne peuvent utiliser les ressources uniquement de l'application principale, mais jamais directement entre elles. Une feature ne dépend jamais d'une autre feature. Ces dernières reprennent chacune les différents packages (domain, infrastructure, kernel, ...) selon leurs besoins. Actuellement, il existe 4 features : - **Invoices** qui gère la génération et récupération des factures lors d'un paiement d'un utilisateur - **Member** qui gère les utilisateurs de TradeMe (CRUD) - **Payment** qui s'occupe d'effectuer les paiements lorsque cela est nécessaire. - **Projects** qui gère les projets de l'application #### Infrastructure ##### Repositories Pour cela on utilise le **pattern repository et strategy** afin de **séparer son interface**, qui restera dans le \*\* domaine\*\*, de son implémentation dans **l'infrastructure**. Actuellement, il existe deux implémentations de cette interface : - Une en mémoire : les entités en mémoire et se vide quand l'application s'arrête. - Une grâce à un fichier JSON, qui persiste à arrêt de l'application et se charge au démarrage. Il est possible de switcher entre l'un et l'autre grâce à la propriété `repository.in-memory=true|false` dans le fichier `application.properties` #### Kernel Ce package contient les différentes interfaces et leurs implémentations de fonctions "utilitaires" qui pourront être exploité par nos services applicatifs et autre afin d'assurer un fonctionnement correct de notre application. ##### IO Ce package nous permet d'avoir les interfaces **Reader** et **Writer** qui vont nous permettre d'interagir avec différents contenus, tel que des fichiers par exemple. Il y a actuellement deux implémentations pour chacune de ces interfaces. Les deux permettent accéder à des fichiers. ##### Marshaller Le package marshaller met à disposition une interface de **Sérialisation** et de **Désérialisation** de nos objets vers une chaine de caractères. Actuellement, il existe une implémentation de chaque pour le format **JSON**. ##### Exception handler Ce package contient également des intercepteurs permettant d'intercepter les exceptions métiers lancés dans le programme afin d'en avoir un traitement centralisé pour logger l'erreur et faire un retour adapté pour l'utilisateur. [![RuntimeExceptionHandler_toResponse.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/runtimeexceptionhandler-toresponse.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/runtimeexceptionhandler-toresponse.png) ##### Validators Apporte des fonctions utilitaires de validation de nos différentes entités du domaine. Exemple : Lors de la vérification des champs, le programme jouera le diagramme de séquence suivant : [![5c7cc1b9735a6d60726007287ea148f0f0509299.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/5c7cc1b9735a6d60726007287ea148f0f0509299.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/5c7cc1b9735a6d60726007287ea148f0f0509299.png) ##### Command Ce package contient la logique d'exécution d'une commande et le bus qui y est associé. Ce dernier possède une implémentation globale à chaque features et est injecté grâce au package configuration qui aura configuré le maillage correctement entre les commandes et les services associés. Le comportement suivant est observé : [![b03d3454f6c7f3dbeb0336faaf94ee45eb074b77.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/b03d3454f6c7f3dbeb0336faaf94ee45eb074b77.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/b03d3454f6c7f3dbeb0336faaf94ee45eb074b77.png) Création d'un contractor : [![ContractorController_register-16417374095051.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/contractorcontroller-register-16417374095051.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/contractorcontroller-register-16417374095051.png) ##### Query Ce package contient la logique d'exécution d'une requête et le bus qui y est associé. Ce dernier possède une implémentation globale à chaque features et est injecté grâce au package configuration qui aura configuré le maillage correctement entre les requêtes et les services associés. Le comportement suivant est observé : [![226848c6adb9c7b64e310525ca2fe3cc8b8c5654.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/226848c6adb9c7b64e310525ca2fe3cc8b8c5654.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/226848c6adb9c7b64e310525ca2fe3cc8b8c5654.png) Récupération d'un contractor : [![ContractorController_getById.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/contractorcontroller-getbyid.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/contractorcontroller-getbyid.png) ##### Event Ce package est essentiel au bon déroulement de notre architecture SEDA ! Il contient la logique du bus d'événement qui permet à tous les observables de s'enregistrer à un événement. Ainsi, lorsque qu'un événement est publié, il est ensuite distribué à tous ses observateurs. Comportement du bus d'événement par défaut : [![DefaultEventBus_publish.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/defaulteventbus-publish.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/defaulteventbus-publish.png) ##### Logger En utilisant le **pattern strategy** ainsi que **factory**, ce package permet à une classe d'obtenir un logger qui lui est propre grâce au **LoggerFactory**. Les interfaces font parties du **domaine** et leurs implémentations de \*\* l'infrastructure\*\*. Actuellement l'implémentation présente réutilise le la classe **Logger** **de Java**. Une deuxième implémentation utilise le logger **JBoss** qui permet d'avoir des logs formatés autrement en console, en plus de les écrire dans un fichier de logs en temps réel afin de garder une trace du comportement de l'application et des éventuelles erreurs. #### Shared kernel Ce package est présent sur le niveau le plus haut de l'application, avant les **features**. Ce dernier contient les entités qui sont partagées entre les différents usecases. #### Web Ce package fournit une interface pour l'utilisateur afin qu'il puisse utiliser l'application à l'aide de requêtes REST. Ce dernier utilise uniquement les command et les requêtes à travers leurs bus associé, et n'a pas connaissance de quoi que ce soit d'autre dans l'application, ce qui lui permet d'avoir très peu de dépendance sur le fonctionnement global, hormis les entités du domaine. Cas d'utilisation, récupération des factures : [![InvoiceController_getAll.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/invoicecontroller-getall.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/invoicecontroller-getall.png) ## Quarkus Pour gagner en puissance dans notre application et avoir des controller web ainsi qu'une **injection de dépendance** puissante, l'application est soutenue par le framework **Quarkus**. Ce dernier sert à : - Gérer les controller web ainsi que le sérialiseur / désérialiser JSON grâce à **Jackson** - Configurer les différentes **bean** pour l'injection de dépendances au sein des différents services (package configuration) - Gérer le **Scheduler** qui permet de lancer les paiements mensuels. - Intégrer le logger **JBoss** plus facilement. - Intégrer **Swagger** plus facilement grâce à des annotations sur les controller - Gérer certain paramètre de l'application à la volée sans devoir recompiler le code tout le temps grâce à un fichier de configuration **application.properties** (configuration swagger, gestion du prix des abonnements, du jour de paiement, du formatage et du stockage des logs). [![image-20220305155344800.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305155344800.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305155344800.png) Le découpage de l'application en amont a permis une intégration très simple de Quarkus. L'application ne dépend pas de quarkus mais utilise simplement le framework comme une implémentation de la solution. ### Dependency Injection Dans le package configuration, les beans sont réparties dans différentes classes : #### GlobalConfiguration C'est lui qui va injecter les bean dites "globales" tel que le logger ou encore la classe contenant les montants pour les abonnements. [![image-20220305153432641.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305153432641.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305153432641.png) #### APIConfiguration Comme son nom l'indique, inject les différents API nécessaires au bon fonctionnement de l'application, actuellement il n'y a que l'API de paiement qui est injecté, mais d'autres peuvent être amené à être créés... [![APIConfiguration.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/apiconfiguration.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/apiconfiguration.png) #### CommandConfiguration Injecte les différents bus de commandes selon la feature, la configuration aura fait au préalable le maillage nécessaire entre les commandes et les services applicatifs. [![image-20220305153609910.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305153609910.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305153609910.png) #### QueryConfiguration Injecte les différents bus de requêtes selon la feature, la configuration aura fait au préalable le maillage nécessaire entre les requêtes et les services applicatifs. [![image-20220305153653788.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305153653788.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305153653788.png) #### EventConfiguration Injecte les différents bus d'événements selon le type d'événement (actuellement uniquement ceux du type \*\* ApplicationEvent\*\*), la configuration aura fait au préalable le maillage nécessaire entre les événements et les observateurs.
[![image-20220305153731926.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305153731926.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305153731926.png)[![EventConfiguration.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/eventconfiguration.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/eventconfiguration.png)
#### RepositoryConfiguration Injecte les différents repositories au sein des services qui en ont besoin : [![image-20220305153814991.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305153814991.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305153814991.png) #### IOConfiguration Injecte l'implémentation choisie pour nos Reader et Writer : [![image-20220305154003138.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305154003138.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305154003138.png) #### MarshallerConfiguration Injecte l'implémentation choisie pour notre Sérialiser et Désérialiser : [![image-20220305154035698.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305154035698.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305154035698.png) ## Tests unitaires Afin d'assurer le bon fonctionnement et grâce au découpage de nos composants, l'application est couverte par des tests unitaires (qui se lancent d'ailleurs automatiquement à chaque push sur github grâce à des **actions de CI**). Nous utilisons CodeCov pour analyser les rapports de tests unitaires au moment de push ou de pull requests [![image-20220109160441842.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220109160441842.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220109160441842.png) [![image-20220305134206216.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305134206216.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305134206216.png)
Branche DEVBranch MAIN
[![codecov](https://codecov.io/gh/Nouuu/AL-TradeMe/branch/dev/graph/badge.svg?token=07VGBLNOBQ)](https://codecov.io/gh/Nouuu/AL-TradeMe)[![codecov](https://codecov.io/gh/Nouuu/AL-TradeMe/branch/main/graph/badge.svg?token=07VGBLNOBQ)](https://codecov.io/gh/Nouuu/AL-TradeMe)
![grid coverage](https://codecov.io/gh/Nouuu/AL-TradeMe/branch/dev/graphs/tree.svg?token=07VGBLNOBQ)![grid coverage](https://codecov.io/gh/Nouuu/AL-TradeMe/branch/main/graphs/tree.svg?token=07VGBLNOBQ)
## Swagger Lorsque l'application est lancée avec le profil **dev** ou **test** cette dernière rend accessible une page web swagger-ui afin de pouvoir tester directement nos interfaces REST et visualiser nos entités pour les requêtes, ainsi que ceux en réponse. Le Swagger est configurables grâce à quelques propriétés dans le fichier `application.properties` : [![image-20220305155045674.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305155045674.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305155045674.png) Le Swagger est accessible à l'adresse suivante lorsque l'application est lancée : http://localhost:8080/q/swagger-ui/#/ [![image-20220305154723783.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220305154723783.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220305154723783.png) ## SonarQube Pour assurer un code propre, le code de l'application est analysé à chaque mise à jour par un serveur Sonarqube autohébergé (https://sonar.nospy.fr) pour assurer que le code rempli bien les critères de maintenabilité, sécurité, fiabilité, ... [![image-20220109155624909.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220109155624909.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220109155624909.png) [![image-20220109155708591.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220109155708591.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220109155708591.png) [![image-20220109155835217.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220109155835217.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220109155835217.png) [![image-20220109155804810.png](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/scaled-1680-/image-20220109155804810.png)](https://wiki2.nospy.fr/uploads/images/gallery/2022-05/image-20220109155804810.png) # Boissibook Dépôts GitHub: - API : [https://github.com/Nouuu/Boissibook](https://github.com/Nouuu/Boissibook) - Scrapper : [https://github.com/RemyMach/Boissibook-scraper](https://github.com/RemyMach/Boissibook-scraper) - Swift App : [https://github.com/RemyMach/boissibook-swift](https://github.com/RemyMach/boissibook-swift)
[![Quality Gate Status](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_Boissibook&metric=alert_status&token=679473caa97f7d7d9df109022b2db2d90c5fea1f)](https://sonar.nospy.fr/dashboard?id=Nouuu_Boissibook&branch=dev)[![Reliability Rating](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_Boissibook&metric=reliability_rating&token=679473caa97f7d7d9df109022b2db2d90c5fea1f)](https://sonar.nospy.fr/dashboard?id=Nouuu_Boissibook&branch=dev)[![Security Rating](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_Boissibook&metric=security_rating&token=679473caa97f7d7d9df109022b2db2d90c5fea1f)](https://sonar.nospy.fr/dashboard?id=Nouuu_Boissibook&branch=dev)[![Technical Debt](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_Boissibook&metric=sqale_index&token=679473caa97f7d7d9df109022b2db2d90c5fea1f)](https://sonar.nospy.fr/dashboard?id=Nouuu_Boissibook&branch=dev)[![Bugs](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_Boissibook&metric=bugs&token=679473caa97f7d7d9df109022b2db2d90c5fea1f)](https://sonar.nospy.fr/dashboard?id=Nouuu_Boissibook&branch=dev)[![Code Smells](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_Boissibook&metric=code_smells&token=679473caa97f7d7d9df109022b2db2d90c5fea1f)](https://sonar.nospy.fr/dashboard?id=Nouuu_Boissibook&branch=dev)[![Coverage](https://sonar.nospy.fr/api/project_badges/measure?branch=dev&project=Nouuu_Boissibook&metric=coverage&token=679473caa97f7d7d9df109022b2db2d90c5fea1f)](https://sonar.nospy.fr/dashboard?id=Nouuu_Boissibook&branch=dev)
- [Concept](#concept) - [Idées de nom](#id%C3%A9es-de-nom) - [Api de recherche de livres](#api-de-recherche-de-livres) - [Dépôts Github](#d%C3%A9p%C3%B4ts-github) - [Architecture Google Cloud Platform & CI/CD](#architecture-google-cloud-platform--cicd) - [Features](#features) - [Gestion des utilisateurs](#gestion-des-utilisateurs) - [Fonctionnalités](#fonctionnalit%C3%A9s) - [Gestion des livres](#gestion-des-livres) - [Fonctionnalités](#fonctionnalit%C3%A9s-1) - [Readlist](#readlist) - [Fonctionnalités](#fonctionnalit%C3%A9s-2) - [Téléchargement et envoie du livre](#t%C3%A9l%C3%A9chargement-et-envoie-du-livre) - [Fonctionnalités](#fonctionnalit%C3%A9s-3) - [Achievements](#achievements) - [Scrapper Zlib](#scrapper-zlib) - [Application frontend](#application-frontend) - [Choix d'implémentations](#choix-d'impl%C3%A9mentations) - [Hexagonal architecture](#hexagonal-architecture) - [Architecture en couche](#architecture-en-couche) - [Diagrammes de séquence](#diagrammes-de-s%C3%A9quence) - [Ajout d'un utilisateur](#ajout-dun-utilisateur) - [Recherche d'un livre](#recherche-dun-livre) - [Ajout d'une review sur un livre](#ajout-dune-review-sur-un-livre) - [Tests](#tests) - [Tests d'architecture](#tests-d'architecture) - [Tests unitaires](#tests-unitaires) - [Tests de contrat avec test container](#tests-de-contrat-avec-test-container) - [Tests E2E](#tests-e2e) ## Concept C'est un utilitaire pour gérer sa collection de livres, à la manière d’un myanimelist, book collector. On peut gérer sa liste de livre, ses statuts de lecture, son avancement… Petit aspect social où l’on peut noter un livre et voir la moyenne de ce dernier donné par les différents utilisateurs. Il sera aussi possible de laisser un commentaire (publique ou pas). Petite fonctionnalité pour pouvoir télécharger l’ebook, et l'ajouter, si on le possède, pour le partager aux autres utilisateurs (tout à fait légal, oui oui.). On pourrait également scraper quelques sites pour essayer de le trouver si on ne le possède pas grâce à un utilitaire intégré (de mieux en mieux !). ### Idées de nom - Boissibook - Kindle surprise, Kindle bueno, maxi ... 😏 - ... ### Api de recherche de livres [Google Books APIs](https://developers.google.com/books/docs/v1/using) [Open Library](https://openlibrary.org/) ## Dépôts Github - [Boissibook](https://github.com/Nouuu/Boissibook) - [Application Swift](https://github.com/RemyMach/boissibook-swift) - [Scrapper Zlib](https://github.com/RemyMach/Boissibook-scraper) ## Architecture Google Cloud Platform & CI/CD L'application est entièrement déployée sur Google Cloud Platform, avec une infrastructure de déploiement automatique. [![README-1658772052065.png](https://wiki.nospy.fr/uploads/images/gallery/2022-10/scaled-1680-/readme-1658772052065.png)](https://wiki.nospy.fr/uploads/images/gallery/2022-10/readme-1658772052065.png) ## Features ### Gestion des utilisateurs Ce usecase est assez classique, elle permet de gérer les utilisateurs. Un utilisateur est défini par les propriétés suivantes : ```json { "userId": { "type": "string", "description": "The user's id" }, "email": { "type": "string", "description": "The user's email", "example": "gregory@mail.com" }, "name": { "type": "string", "description": "The user's name", "example": "Gregory" } } ``` #### Fonctionnalités Les différentes fonctions sont les suivantes : - Créer un utilisateur - Modifier un utilisateur - Supprimer un utilisateur - Supprimer tous les utilisateurs - Récupérer un utilisateur - Récupérer un utilisateur par son email - Récupérer la liste des utilisateurs - Compter le nombre d’utilisateurs ### Gestion des livres Feature permettant de chercher un livre, l’ajouter à la base s’il n’existe pas encore et récupérer les informations de ce dernier (y compris sa note et les commentaires publics laissés par les utilisateurs). Un livre est défini par les propriétés suivantes : ```json { "id": { "type": "string" }, "title": { "type": "string" }, "authors": { "type": "array", "items": { "type": "string" } }, "publisher": { "type": "string" }, "publishedDate": { "type": "string" }, "description": { "type": "string" }, "isbn13": { "type": "string" }, "language": { "type": "string" }, "imgUrl": { "type": "string" }, "pages": { "type": "integer", "format": "int32" } } ``` #### Fonctionnalités Les différentes fonctions sont les suivantes : - Chercher un livre en une ligne (qui pourra prendre aussi bien le nom d’un livre que celui d’un auteur, d’un genre) . - Chercher en ligne par ISBN - Enregistrer un livre en base (par ISBN) - Chercher un livre en base en une ligne (qui pourra prendre aussi bien le nom d’un livre que celui d’un auteur, d’un genre). - Supprimer un livre en base - Récupérer les informations d’un livre en base (par ISBN) - Récupérer les commentaires (public) d’un livre en base (par ISBN) ### Readlist Feature permettant à un utilisateur de gérer sa bibliothèque et ses livres en cours de lecture. Une review est définie par les propriétés suivantes : ```json { "bookProgressionId": { "type": "string", "description": "The id of the readlist item", "example": "7bd1b206-833d-4378-8064-05b162d80764" }, "bookId": { "type": "string", "description": "The id of the book", "example": "7bd1b206-833d-4378-8064-05b162d80764" }, "userId": { "type": "string", "description": "The id of the user", "example": "7bd1b206-833d-4378-8064-05b162d80764" }, "readingStatus": { "type": "string", "description": "The reading status of the book", "example": "READING" }, "visibility": { "type": "string", "description": "The visibility of the review", "example": "PUBLIC" }, "currentPage": { "type": "integer", "description": "The number of the current page", "format": "int32", "example": 12 }, "note": { "type": "integer", "description": "The note given to the book", "format": "int32", "example": 5 }, "comment": { "type": "string", "description": "The comment of the review", "example": "This book is awesome" } } ``` #### Fonctionnalités Les différentes fonctions sont les suivantes : - Récupérer une review par son id - Mettre à jour sa review sur un livre - Supprimer sa review sur un livre - Ajouter une review sur un livre - Mettre à jour son statut de lecture sur un livre - Mettre à jour sa note sur un livre - Mettre à jour son commentaire sur un livre - Mettre à jour sa progression sur un livre - Récupérer toutes les reviews d’un utilisateur - Récupérer toutes les reviews d’un livre ### Téléchargement et envoie du livre La fonctionnalité phare et tout à fait légale (🤡) de Boissibook. Il est possible d'ajouter sa propre version numérique d'un livre. Si vous ne possédez pas le livre, pas de problème ! Un autre utilisateur l'a peut être déjà ajouté à votre place. Sinon, vous pouvez demander à Boissibook de tenter de le télécharger pour vous (dans la limite du quota de 5 par jours). Un fichier de livre est défini par les propriétés suivantes : ```json { "id": { "type": "string", "description": "Book file id" }, "name": { "type": "string", "description": "Book file name" }, "type": { "type": "string", "description": "Book file type" }, "bookId": { "type": "string", "description": "Book id" }, "userId": { "type": "string", "description": "User who uploaded id" }, "downloadCount": { "type": "integer", "description": "File download count", "format": "int32" } } ``` #### Fonctionnalités - Ajouter sa version numérique d'un livre - Trouver un fichier via Zlib → [Scrapper Zlib](#scrapper-zlib) - Récupérer la liste des liens de téléchargement (ordonnée par nombre de téléchargements) d'un livre - Récupérer le nombre de fichiers de livres disponibles pour un livre - Supprimer un fichier livre - Télécharger un livre ### Achievements Pour un peu plus de FUN, Boissibook propose des achievements. Ces derniers s'obtiennent lorsque vous avez terminé un certain nombre de livres ou qu'un de vos livres ajouté à la bibliothèque a été téléchargé plusieurs fois. ### Scrapper Zlib Scrapper python, utilisé par l’application Spring pour parcourir Zlib et télécharger le bouquin grâce à son nom, ISBN. - [FastAPI](https://fastapi.tiangolo.com/) - [Selenium](https://fr.acervolima.com/principes-de-base-de-selenium-python/) - Ou [beautifulsoup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) Une fois le fichier du livre récupéré → [Téléchargement / Envoie du livre ](#t%C3%A9l%C3%A9chargement-et-envoie-du-livre) ## Application frontend Afin de pouvoir exploiter la puissance de Boissibook, nous avons développé une application IOS en Swift.
[![README-1658842903759.png](https://wiki.nospy.fr/uploads/images/gallery/2022-10/scaled-1680-/readme-1658842903759.png)](https://wiki.nospy.fr/uploads/images/gallery/2022-10/readme-1658842903759.png) [![README-1658843850902.png](https://wiki.nospy.fr/uploads/images/gallery/2022-10/scaled-1680-/readme-1658843850902.png)](https://wiki.nospy.fr/uploads/images/gallery/2022-10/readme-1658843850902.png)
[![README-1658843859147.png](https://wiki.nospy.fr/uploads/images/gallery/2022-10/scaled-1680-/readme-1658843859147.png)](https://wiki.nospy.fr/uploads/images/gallery/2022-10/readme-1658843859147.png) [![README-1658843868080.png](https://wiki.nospy.fr/uploads/images/gallery/2022-10/scaled-1680-/readme-1658843868080.png)](https://wiki.nospy.fr/uploads/images/gallery/2022-10/readme-1658843868080.png)
## Choix d'implémentations ### Hexagonal architecture L’objectif principal de l’architecture hexagonale est de découpler la partie métier d’une application de ses services techniques. Ceci dans le but de préserver la partie métier pour qu’elle ne contienne que des éléments liés aux traitements fonctionnels. Cette architecture est aussi appelée “Ports et Adaptateurs” car l’interface entre la partie métier et l’extérieur se fait, d’une part, en utilisant les ports qui sont des interfaces définissant les entrées ou sorties et d’autre part, les adaptateurs qui sont des objets adaptant le monde extérieur à la partie métier. #### Architecture en couche L’architecture hexagonale préconise une version simplifiée de l’architecture en couches pour séparer la logique métier des processus techniques. La logique métier doit se trouver à l’intérieur de l’hexagone. Nous prenons plusieurs concepts en compte pour affiner cette architecture tel que : - Inversion de dépendances - Couche applicative - Couche infrastructure - ... La couche applicative ne doit contenir que le métier de notre application, toutes ses dépendances doivent ainsi être des interfaces métiers, qui seront ensuite injectées et implémentées par la couche infrastructure.
CouchesCouche Applicative
[![README-1658825410490.png](https://wiki.nospy.fr/uploads/images/gallery/2022-10/scaled-1680-/readme-1658825410490.png)](https://wiki.nospy.fr/uploads/images/gallery/2022-10/readme-1658825410490.png) [![README-1658825959474.png](https://wiki.nospy.fr/uploads/images/gallery/2022-10/scaled-1680-/readme-1658825959474.png)](https://wiki.nospy.fr/uploads/images/gallery/2022-10/readme-1658825959474.png)
### Diagrammes de séquence Voici quelques diagrammes de séquence montrant un workflow "Classique" de nos cas d'utilisations. #### Ajout d'un utilisateur Lors de l'ajout d'un utilisateur, plusieurs choses se déroulent : 1. Transformation de l'objet utilisateur provenant de la requête en un objet utilisateur métier. 2. Appel du service métier d'enregistrement de l'utilisateur. 3. Récupération d'un nouvel ID pour l'enregistrement de l'utilisateur. 4. Enregistrement de l'utilisateur dans la base de données. 5. Envoie d'un événement de création d'utilisateur. 6. Retour au client de confirmation de l'enregistrement de l'utilisateur, avec en en-tête le lien pour consulter l'utilisateur créé. [![UserCommandController_createUser.png](https://wiki.nospy.fr/uploads/images/gallery/2022-10/scaled-1680-/usercommandcontroller-createuser.png)](https://wiki.nospy.fr/uploads/images/gallery/2022-10/usercommandcontroller-createuser.png) #### Recherche d'un livre Lorsque l'on cherche un livre à travers l'API (Recherche google), plusieurs choses se déroulent : 1. Récupération du terme de la recherche 2. Appel du service métier de recherche de livre. 3. Appel du moteur de recherche (en l'occurrence, celui de Google) 4. Récupération des résultats de la recherche. 5. Transformation de l'objet de résultat de la recherche en un objet de résultat de recherche métier. 6. Renvoie des résultats de la recherche au client. [![BookSearchRequestController_search.png](https://wiki.nospy.fr/uploads/images/gallery/2022-10/scaled-1680-/booksearchrequestcontroller-search.png)](https://wiki.nospy.fr/uploads/images/gallery/2022-10/booksearchrequestcontroller-search.png) #### Ajout d'une review sur un livre L'ajout d'une review sur un livre se passe comme suit : 1. Transformation de l'objet review provenant de la requête en un objet review métier. 2. Vérification de l'existence du livre 3. Vérification de l'existence de l'utilisateur 4. Vérification de l'existence d'une précédente review pour cet utilisateur sur ce livre (Si c'est le cas on déclenche une erreur). 5. Appel du service métier d'enregistrement de la review. 6. Récupération d'un nouvel ID pour l'enregistrement de la review. 7. Enregistrement de la review dans la base de données. 8. Envoie d'un événement de création de review. 9. Retour au client de confirmation de l'enregistrement de la review, avec en en-tête le lien pour consulter la review créée. [![ReadlistCommandController_createBookReview.png](https://wiki.nospy.fr/uploads/images/gallery/2022-10/scaled-1680-/readlistcommandcontroller-createbookreview.png)](https://wiki.nospy.fr/uploads/images/gallery/2022-10/readlistcommandcontroller-createbookreview.png) ### Tests Afin de garantir que notre application fonctionne correctement, nous avons mis en place plusieurs types de tests. Ces derniers sont automatiquement exécutés lorsque nous faisons un nouveau déploiement et peut interrompre ce dernier s'ils ne se valident pas tous. #### Tests d'architecture Grâce à la librairie **Arch Unit**, nous vérifions que notre application respecte les spécifications de l'architecture hexagonale. Pour ce faire, nous allons valider trois choses : - Les éléments du domaine ne doivent jamais importer d'éléments du framework **Spring** ou **Javax** - Les éléments du kernel ne doivent jamais importer d'éléments du framework **Spring** ou **Javax** - L'infra peut importer des éléments du framework **Spring** ou **Javax** ou du domaine, mais pas l'inverse. > Exemple d'un test avec **Arch Unit** ```java class ArchitectureTest { @Test void should_domain_never_be_linked_with_frameworks() { var ruleNoFramework = noClasses().that().resideInAPackage("..domain..") .should().dependOnClassesThat().resideInAPackage("..springframework..") .orShould().dependOnClassesThat().resideInAPackage("javax.."); ruleNoFramework.check(projectClasses); } } ``` #### Tests unitaires Nous avons décidé de mettre en place des tests unitaires pour nos classes de domaine. Nos tests unitaires sont complètement séparés du framework **Spring** et **Javax**, ce dernier n'est absolument pas présent. > Exemple de tests unitaires sur la partie utilisateur ```java class UserCommandHandlerTest { // ... @BeforeEach void setUp() { userRepository = new InMemoryUserRepository(); userCommandHandler = new UserCommandHandler(userRepository, new VoidEventService()); // ... } @Test void createUser() { var userId = userCommandHandler.createUser(user1); assertThat(userId).isNotNull(); assertThat(userRepository.find(userId)) .isNotNull() .isEqualTo(user1.setId(userId)); } @Test void updateUser() { userRepository.save(user1.setId(userRepository.nextId())); user1.setName("newName") .setPassword(null); userCommandHandler.updateUser(user1); assertThat(userRepository.find(user1.id())) .isEqualTo(user1.setPassword("password")); } // ... } ``` #### Tests de contrat avec test container Dans le cas de nos implémentations de nos interfaces de **Repository**, nous souhaitons tester le bon fonctionnement de nos méthodes faisant appel à la base de donnée. Pour se mettre en situation réelle, il nous faut donc une vraie base de donnée pour effectuer nos tests. De plus, nous avons également une implémentation de nos **Repositories** en mémoire et nous devons nous assurer que cette dernière a le même comportement que la base de donnée. Ainsi nous nous assurons de ne pas avoir de comportements inattendus en changeant d'une implémentation à l'autre. Cela nous permet également pour les autres tests unitaires de n'utiliser que la base en mémoire pour nous affranchir totalement de Spring, sans prendre le risque de passer à côté de quelque chose. Pour ce faire, nous allons utiliser la librairie [Testcontainers](https://www.testcontainers.org) pour pouvoir monter à la volée un conteneur Docker d'une base de donnée entièrement dédiée aux tests. Lorsqu'une classe de tests nécessite une base de donnée, nous allons lui faire implémenter l'interface suivante afin de lui faire monter un conteneur docker. > PostgresIntegrationTest ```java public abstract class PostgresIntegrationTest { private static final PostgreSQLContainer POSTGRES_SQL_CONTAINER; static { POSTGRES_SQL_CONTAINER = new PostgreSQLContainer<>(DockerImageName.parse("postgres:14-alpine")); POSTGRES_SQL_CONTAINER.start(); } @DynamicPropertySource static void overrideTestProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", POSTGRES_SQL_CONTAINER::getJdbcUrl); registry.add("spring.datasource.username", POSTGRES_SQL_CONTAINER::getUsername); registry.add("spring.datasource.password", POSTGRES_SQL_CONTAINER::getPassword); } } ``` Cette dernière va venir surcharger les paramètres Spring pour lui faire se connecter à la base de donnée automatiquement. > UserRepositoryTest ```java @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) @Testcontainers @SpringBootTest @ActiveProfiles("test") class UserRepositoryTest extends PostgresIntegrationTest { @Autowired JPAUserRepository jpaUserRepository; private final static String springDataUserRepositoryKey = "SpringDataUserRepository"; private final static String inMemoryUserRepositoryKey = "InMemoryUserRepository"; private HashMap userRepositories; // ... @BeforeEach void setUp() { SpringDataUserRepository userRepository = new SpringDataUserRepository(jpaUserRepository); InMemoryUserRepository inMemoryUserRepository = new InMemoryUserRepository(); userRepositories = new HashMap<>(); userRepositories.put(springDataUserRepositoryKey, userRepository); userRepositories.put(inMemoryUserRepositoryKey, inMemoryUserRepository); // ... } private static Stream provideRepositories() { return Stream.of( springDataUserRepositoryKey, inMemoryUserRepositoryKey ); } @ParameterizedTest @MethodSource("provideRepositories") void save(String userRepositoryKey) { UserRepository userRepository = userRepositories.get(userRepositoryKey); userRepository.save(user1); assertThat(userRepository.find(user1.id())) .isEqualTo(user1); } } ``` Grâce aux `ParameterizedTest`, nous allons jouer les mêmes tests aussi bien sur la base de donnée réelle que celle en mémoire, afin de nous assurer que chacune valide exactement les mêmes tests. #### Tests E2E Afin de pouvoir tester les fonctionnalités de notre application, nous devons tester que l'application fonctionne de bout en bout avec un cas d'utilisation réel. Il faut alors lancer l'application avec tout le context **Spring**, ainsi qu'avec **Testcontainers** pour avoir un comportement réel. Nous nous servons ensuite de la librairie [RestAssured](https://rest-assured.io) pour faire des requêtes sur l'API afin de s'assurer que le comportement est bien celui attendu. > UserCommandsAPITest ```java @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) @Testcontainers @SpringBootTest(webEnvironment = RANDOM_PORT) @ActiveProfiles("test") class UserCommandsAPITest extends PostgresIntegrationTest { //... @LocalServerPort int port; @BeforeEach void setUp() { RestAssured.port = port; RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter()); } @Test void createUser() { var getUserUri = given() .contentType(JSON) .body(validUser1) .when() .post("/users") .then() .statusCode(201) .extract() .header("location"); var user = given() .baseUri(getUserUri) .when() .get() .then().statusCode(200) .extract() .body().as(UserResponse.class); assertThat(user.userId()).isNotNull(); assertThat(user.email()).isEqualTo(validUser1.email()); assertThat(user.name()).isEqualTo(validUser1.name()); } //... } ```