# 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 variable | Default   | Description                             |
|----------------------|-----------|-----------------------------------------|
| PORT                 | 3000      | Express listen port                     |
| DB_DRIVER            | mysql     | Driver for sql connection for sequelize |
| DB_HOST              | localhost | Host domain / IP for DB                 |
| DB_NAME              | zoo       | DB Schema name                          |
| DB_USER              | zoo       | DB user                                 |
| DB_PASSWORD          | `empty`   | DB password                             |

### Main dependencies

| Dependency         | Version                                                                                                                      | Description                                                                |
|--------------------|------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|
| 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)

<table id="bkmrk-"><thead><tr><th>[![github](https://img.shields.io/badge/repository-github-blue)](https://github.com/Nouuu/AL-TradeMe)</th></tr></thead></table>

<table id="bkmrk-main-status"><thead><tr><th>Main status</th><th>[![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)</th><th>[![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)</th><th>[![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)</th><th>[![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)</th><th>[![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)</th></tr></thead><tbody><tr><td>  
</td><td>  
</td><td>[![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)</td><td>  
</td><td>[![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)</td><td>[![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)</td></tr><tr><td>  
</td><td>  
</td><td>[![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)</td><td>  
</td><td>  
</td><td>  
</td></tr></tbody></table>

<table id="bkmrk-dev-status"><thead><tr><th>Dev status</th><th>[![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)</th><th>[![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)</th><th>[![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)</th><th>[![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)</th><th>[![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)</th></tr></thead><tbody><tr><td>  
</td><td>  
</td><td>[![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)</td><td>  
</td><td>[![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)</td><td>[![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)</td></tr><tr><td>  
</td><td>  
</td><td>[![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)</td><td>  
</td><td>  
</td><td>  
</td></tr></tbody></table>

## 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.

<table id="bkmrk--16"><thead><tr><th>[![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)</th><th>[![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)</th></tr></thead></table>

#### 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)

<table id="bkmrk-branche-dev-branch-m"><thead><tr><th>Branche DEV</th><th>Branch MAIN</th></tr></thead><tbody><tr><td>[![codecov](https://codecov.io/gh/Nouuu/AL-TradeMe/branch/dev/graph/badge.svg?token=07VGBLNOBQ)](https://codecov.io/gh/Nouuu/AL-TradeMe)</td><td>[![codecov](https://codecov.io/gh/Nouuu/AL-TradeMe/branch/main/graph/badge.svg?token=07VGBLNOBQ)](https://codecov.io/gh/Nouuu/AL-TradeMe)</td></tr><tr><td>![grid coverage](https://codecov.io/gh/Nouuu/AL-TradeMe/branch/dev/graphs/tree.svg?token=07VGBLNOBQ)</td><td>![grid coverage](https://codecov.io/gh/Nouuu/AL-TradeMe/branch/main/graphs/tree.svg?token=07VGBLNOBQ)</td></tr></tbody></table>

## 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)

<table id="bkmrk-"><thead><tr><th>[![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)</th><th>[![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)</th><th>[![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)</th><th>[![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)</th><th>[![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)</th><th>[![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)</th><th>[![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)</th></tr></thead></table>

- [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 &amp; 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 &amp; 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.

<table id="bkmrk--1"><thead><tr><th>[![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)

</th><th class="align-center" style="vertical-align:middle;">[![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)

</th></tr></thead><tbody><tr><td>[![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)

</td><td>[![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)

</td></tr></tbody></table>

## 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.

<table id="bkmrk-couches-couche-appli"><thead><tr><th>Couches</th><th>Couche Applicative</th></tr></thead><tbody><tr><td>[![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)

</td><td>[![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)

</td></tr></tbody></table>

### 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<String, UserRepository> 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<String> 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());
    }
    //...
}

```