Skip to content
Snippets Groups Projects
Commit 5151b80b authored by narindra.rajohnso's avatar narindra.rajohnso
Browse files

finish rapport

parent ceee46ee
No related branches found
No related tags found
No related merge requests found
Showing
with 247 additions and 109 deletions
# projet_isc_l431 - documentation # Documentation Quizz Game
Ce repo git contient la documentation pour le projet réalisé dans le cadre du cours d'ateliers logiciels. La documentation est effectué avec [MkDocs](https://www.mkdocs.org) permettant de facilement visualiser la documentation (en local).
Ce repo contient également un fichier de configuration [MkDocs](https://www.mkdocs.org) permettant de facilement visualiser la documentation (en local).
## Installation du repo ## Installation du repo
......
...@@ -11,7 +11,7 @@ Permet de récupérer les informations d'un administrateur qui est actuellement ...@@ -11,7 +11,7 @@ Permet de récupérer les informations d'un administrateur qui est actuellement
| Méthode d'authentification supporté par ce endpoint | [OAuth 2.0 Bearer Token](https://oauth.net/2/bearer-tokens/) | | Méthode d'authentification supporté par ce endpoint | [OAuth 2.0 Bearer Token](https://oauth.net/2/bearer-tokens/) |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token` <span class="bg-blue-200">REQUIRED</span> | Générer par le serveur lors de l'authentification, protéger par une clé qui est présente du côté du serveur. Ce token est envoyé par le client dans son header (Authorization: Bearer `token`) | | `token` <br><span class="bg-blue-200">REQUIRED</span> | Générer par le serveur lors de l'authentification, protéger par une clé qui est présente du côté du serveur. Ce token est envoyé par le client dans son header (Authorization: Bearer `token`) |
...@@ -19,7 +19,7 @@ Permet de récupérer les informations d'un administrateur qui est actuellement ...@@ -19,7 +19,7 @@ Permet de récupérer les informations d'un administrateur qui est actuellement
| Nom | Type | Description | | Nom | Type | Description |
| ---------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------ | | ---------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------ |
| `username` <span class="bg-blue-200">REQUIRED</span> | string | Nom d'utilisateur qui est vérifié par le serveur, si elle n'existe pas une erreur survient | | `username` <br><span class="bg-blue-200">REQUIRED</span> | string | Nom d'utilisateur qui est vérifié par le serveur, si elle n'existe pas une erreur survient |
## Exemple d'utilisation ## Exemple d'utilisation
......
...@@ -11,7 +11,7 @@ Permet de récupérer les informations d'un utilisateur qui est actuellement aut ...@@ -11,7 +11,7 @@ Permet de récupérer les informations d'un utilisateur qui est actuellement aut
| Méthode d'authentification supporté par ce endpoint | [OAuth 2.0 Bearer Token](https://oauth.net/2/bearer-tokens/) | | Méthode d'authentification supporté par ce endpoint | [OAuth 2.0 Bearer Token](https://oauth.net/2/bearer-tokens/) |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token` <span class="bg-blue-200">REQUIRED</span> | Générer par le serveur lors de l'authentification, protéger par une clé qui est présente du côté du serveur. Ce token est envoyé par le client dans son header (Authorization: Bearer `token`) | | `token` <br><span class="bg-blue-200">REQUIRED</span> | Générer par le serveur lors de l'authentification, protéger par une clé qui est présente du côté du serveur. Ce token est envoyé par le client dans son header (Authorization: Bearer `token`) |
...@@ -19,7 +19,7 @@ Permet de récupérer les informations d'un utilisateur qui est actuellement aut ...@@ -19,7 +19,7 @@ Permet de récupérer les informations d'un utilisateur qui est actuellement aut
| Nom | Type | Description | | Nom | Type | Description |
| ---------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------ | | ---------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------ |
| `username` <span class="bg-blue-200">REQUIRED</span> | string | Nom d'utilisateur qui est vérifié par le serveur, si elle n'existe pas une erreur survient | | `username` <br><span class="bg-blue-200">REQUIRED</span> | string | Nom d'utilisateur qui est vérifié par le serveur, si elle n'existe pas une erreur survient |
## Exemple d'utilisation ## Exemple d'utilisation
......
# Explication de l'API
\ No newline at end of file
...@@ -10,12 +10,12 @@ Permet de créer un compte utilisateur ou administrateur, avec une vérification ...@@ -10,12 +10,12 @@ Permet de créer un compte utilisateur ou administrateur, avec une vérification
| Nom | Type | Description | | Nom | Type | Description |
| ------------------------------------------------------- | ------ | ------------------------------------------------------------ | | ------------------------------------------------------- | ------ | ------------------------------------------------------------ |
| `username` <span class="bg-blue-200">REQUIRED</span> | string | Nom d'utilisateur unique pour le compte | | `username` <br><span class="bg-blue-200">REQUIRED</span> | string | Nom d'utilisateur unique pour le compte |
| `password` <span class="bg-blue-200">REQUIRED</span> | string | Mot de passe du compte | | `password` <br><span class="bg-blue-200">REQUIRED</span> | string | Mot de passe du compte |
| `firstname` <span class="bg-blue-200">REQUIRED</span> | string | Prénom de l'utilisateur | | `firstname` <br><span class="bg-blue-200">REQUIRED</span> | string | Prénom de l'utilisateur |
| `lastname` <span class="bg-blue-200">REQUIRED</span> | string | Nom de famille de l'utilisateur | | `lastname` <br><span class="bg-blue-200">REQUIRED</span> | string | Nom de famille de l'utilisateur |
| `email` <span class="bg-blue-200">REQUIRED</span> | string | Adresse e-mail de l'utilisateur | | `email` <br><span class="bg-blue-200">REQUIRED</span> | string | Adresse e-mail de l'utilisateur |
| `accountType` <span class="bg-blue-200">REQUIRED</span> | string | Type de compte (utilisateur(**0**) ou administrateur(**1**)) | | `accountType` <br><span class="bg-blue-200">REQUIRED</span> | string | Type de compte (utilisateur(**0**) ou administrateur(**1**)) |
!!! warning "Attention" !!! warning "Attention"
......
# Backend
Le backend de l'application est responsable de la gestion des connexions Socket.IO, de la communication en temps réel entre les joueurs, de la logique du jeu et de l'accès à la base de données. Dans ce rapport, nous allons expliquer les principales fonctionnalités, la logique du code et la configuration de la base de données.
## Structure du code
Le code du backend est organisé en plusieurs fichiers :
- `ServerIO.ts` : Ce fichier contient la classe ServerIO qui étend la classe IO.Server de `Socket.IO`. Il gère les connexions et les événements liés aux sockets.
- `UserInfo.ts` : Ce fichier définit la classe UserInfo, qui représente les informations d'un joueur.
- `Database.ts` : Ce fichier gère l'accès à la base de données. Il utilise les technologies suivantes :
- *Sequelize* : un ORM (Object-Relational Mapping) pour interagir avec la base de données relationnelle.
- *SQLite* : un système de gestion de base de données relationnelle.
## Configuration de la base de données
La base de données est créée à l'aide de MySQL, un système de gestion de base de données relationnelle. Le backend utilise Sequelize comme ORM pour interagir avec la base de données.
La configuration de la base de données est effectuée dans le fichier `Database.ts`. La classe Database contient des méthodes pour se connecter à la base de données, définir les modèles et effectuer des requêtes.
Les modèles Sequelize sont utilisés pour représenter les tables de la base de données. Dans le contexte du jeu de quiz, il existe un modèle Question qui représente les questions du jeu. Ce modèle est utilisé pour effectuer des opérations de lecture (récupérer les questions aléatoirement) à partir de la base de données.
## Classe `ServerIO`
La classe `ServerIO` est responsable de la gestion des connexions Socket.IO, de la logique du jeu et de la communication entre les joueurs. Voici une description des principales fonctionnalités de cette classe :
## Initialisation du jeu
Lorsqu'un joueur se connecte, la méthode `initializeGame` est appelée pour initialiser les informations du joueur et vérifier s'il existe déjà dans le dictionnaire des joueurs connectés. Si le joueur est nouveau, il est ajouté au dictionnaire `players`. De plus, les propriétés `playersReady` et `playersScore` sont initialisées pour ce joueur.
## Événements Socket.IO
La classe ServerIO enregistre les événements Socket.IO sur chaque socket connecté. Voici les principaux événements enregistrés :
- L'événement **"player-ready"** est déclenché lorsque le joueur est prêt à jouer. Le dictionnaire `playersReady` est mis à jour pour ce joueur, et si tous les joueurs sont prêts, l'événement **"game-ready-start"** est émis.
- L'événement **"player-not-ready"** est déclenché lorsque le joueur n'est plus prêt à jouer. Le dictionnaire `playersReady` est mis à jour, et si tous les joueurs ne sont plus prêts, l'événement **"not-ready-player"** est émis.
- L'événement **"on-game"** est déclenché lorsque les trois joueurs sont prêts. Une question est sélectionnée aléatoirement à l'aide de la méthode `getRandomQuestion`, puis l'événement **"question"** est émis pour envoyer la question aux joueurs.
- L'événement **"validate-question"** est déclenché lorsque les joueurs répondent à une question. La réponse sélectionnée est comparée à la réponse correcte de la question en cours. Les scores des joueurs sont mis à jour en fonction de la réponse, et une nouvelle question est envoyée si le nombre de questions n'a pas atteint la limite de 10.
- L'événement **"restart-game"** est déclenché lorsque les joueurs veulent redémarrer le jeu. Si le nombre de joueurs est toujours de trois, les dictionnaires `players`, `playersScore` et `playersReady` sont réinitialisés, et le jeu est initialisé à nouveau pour chaque joueur.
- L'événement **"disconnect"** est déclenché lorsqu'un joueur se déconnecte. Les informations du joueur sont supprimées des dictionnaires `players`, `playersReady` et `playersScore`.
## Communication entre joueurs
La classe `ServerIO` utilise des événements Socket.IO pour permettre la communication entre les joueurs. Les événements **"players"** et **"players-left"** sont émis pour informer les joueurs connectés et les joueurs restants respectivement. L'événement **"start-game"** est émis lorsque trois joueurs sont connectés pour démarrer le jeu.
## Accès à la base de données
La classe ServerIO utilise la méthode `getRandomQuestion` pour récupérer une question aléatoire à partir de la base de données. Cette méthode utilise le modèle Question défini dans le fichier Database.ts pour effectuer une requête de sélection aléatoire.
## Conclusion
Le backend de l'application gère les connexions Socket.IO, la logique du jeu et l'accès à la base de données MySQL. Il utilise la classe ServerIO pour gérer les connexions, enregistrer les événements et gérer l'état du jeu. L'ORM Sequelize facilite l'accès à la base de données et permet d'effectuer des opérations CRUD (Create, Read, Update, Delete) sur les tables.
L'utilisation de Socket.IO permet une communication en temps réel entre les joueurs, ce qui crée une expérience de jeu interactive. La base de données est utilisée pour stocker les questions du jeu, et le backend utilise Sequelize pour interagir avec la base de données de manière efficace.
# Frontend
## Introduction
Pour la réalisation du frontend, on a utiliser Angular et *Tailwind CSS*. On a fait ce choix car *Tailwind CSS* permet d'utiliser de classes utilitaires pour styliser les éléments HTML spécifique, ce qui nous permet de construire notre interface en combinant ces classes.
## Organisation du code
### Structure du projet Angular
Le projet a été structuré de manière à pouvoir se retrouver facilement dans le code, en séparant le projets en diverses composants:
```markdown
- app
- login
- error-login
- sign-in
- sign-up
- login.service.ts
- session.service.ts
- token-interceptor.service.ts
- manage
- users:
- list-users
- create-user
- edit-user
- questions:
- list-questions
- create-question
- edit-question
- component:
- alert-add
- delete-item
- loading
- account-details
- manage.service.ts
- homepage
- waiting-players
- quizz-play
- quizz.service.ts
- app.component.ts
- app.component.html
- app.module.ts
- app-routing.module.ts
```
### Routes du projet
Voici les différentes routes présentes sur notre application:
```typescript
const routes: Routes = [
{ path: '', redirectTo: 'login', pathMatch: 'full' },
{
path: 'login',
component: LoginComponent,
children: [
{
path: '',
redirectTo: '/login/sign-in',
pathMatch: 'full'
},
{
path: 'sign-in',
component: SignInComponent
},
{
path: 'sign-up',
component: SignUpComponent
}
]
},
{ path: ':admin/manage',
component: ManageComponent,
children: [
{
path: '',
redirectTo: 'list-users',
pathMatch: 'full'
},
{
path: 'list-users',
component: ListUsersComponent
},
{
path: 'list-questions',
component: ListQuestionsComponent
},
{
path: 'account-details',
component: AccountDetailsComponent
}
]
},
{ path: ':username/play',
component: HomepageComponent,
children: [
{
path: '',
redirectTo: 'waiting-players',
pathMatch: 'full'
},
{
path: 'waiting-players',
component: WaitingPlayersComponent
},
{
path: 'quizz-play',
component: QuizzPlayComponent
}
]
}
];
```
## Implémentation du login
Dans cette partie, nous avons implémenté le composant de login ainsi que la gestion des erreurs de login et le stockage des informations de session utilisateur côté frontend.
### Création du composant de login
Le composant de login est implémenté dans le fichier `sign-in.component.html et sign-in.component.ts`. Il contient un formulaire de login avec des champs pour le nom d'utilisateur, le mot de passe et une case à cocher pour se souvenir de l'utilisateur. Lorsque le formulaire est soumis, la méthode `submitForm()` est appelée pour valider les identifiants et effectuer l'authentification. Si l'invité n'a pas de compte, il peut en créer un grâce au composant `sign-up`.
### Validation des identifiants et authentification côté frontend
La méthode `submitForm()` dans le fichier `sign-in.component.ts` est responsable de la validation des identifiants et de l'authentification côté frontend. Elle envoie une requête HTTP POST au serveur avec les informations de login saisies par l'utilisateur. Si les identifiants sont valides, l'utilisateur est redirigé vers la page appropriée en fonction de son rôle (utilisateur ou administrateur).
### Gestion des erreurs de login
Le composant `errorlogin` est utilisé pour afficher les messages d'erreur de login. Le composant reçoit les informations d'erreur depuis le service `loginService` et affiche le message correspondant en fonction du type d'erreur. Par exemple, si l'erreur est liée à un mot de passe incorrect, le message "Le mot de passe que vous avez saisi est incorrect, veuillez réessayer" est affiché.
### Stockage des informations de session utilisateur
Après une authentification réussie, les informations de session utilisateur sont stockées dans le service ``sessionSevice`. Les informations comprennent le nom d'utilisateur, le prénom, le nom et le jeton d'authentification. Ces informations peuvent être utilisées ultérieurement dans d'autres parties de l'application pour personnaliser l'expérience utilisateur.
Ces fonctionnalités permettent aux utilisateurs de se connecter à l'application en fournissant leurs identifiants, de gérer les erreurs de login et de stocker les informations de session pour une utilisation ultérieure.
## Page de gestion par l'administrateur
L'objectif de la page de gestion par l'administrateur est de permettre à un administrateur du système d'interagir avec les données du système, notamment les questions et les utilisateurs. Cette page offre des fonctionnalités de création, de mise à jour, de suppression et d'affichage des informations.
### Création du composant de gestion
Dans le développement de la page de gestion, un composant spécifique(`manage`) a été créé pour gérer les opérations liées aux questions et aux utilisateurs. Ce composant permet à l'administrateur d'accéder aux fonctionnalités de manipulation des données et facilite l'interaction avec l'API REST. Ce composant va également integrer deux composants(`list-questions` et `list-users`), qui vont permettre chacun de gérer pour l'un les questions et pour l'autre les utilisateurs.
### Récupération des données côté frontend à partir de l'API REST
Pour afficher les informations sur la page de gestion, les données sont récupérées depuis l'API REST. L'application frontend envoie des requêtes à l'API pour obtenir les questions et les utilisateurs enregistrés dans le système. Ces données sont ensuite affichées dans une interface d'administration conviviale.
### Affichage des questions et des utilisateurs dans une interface d'administration
Une fois les données récupérées, elles sont affichées dans une interface d'administration conviviale. Les questions et les utilisateurs sont présentés de manière structurée, facilitant ainsi la lecture et la gestion des informations par l'administrateur. Cela permet à l'administrateur de visualiser rapidement les questions et les utilisateurs enregistrés dans le système.
### Mise à jour et suppression des questions et des utilisateurs
En plus de l'affichage des questions et des utilisateurs, l'interface d'administration offre également des fonctionnalités de mise à jour et de suppression. L'administrateur peut modifier les détails d'une question ou d'un utilisateur existant, et il peut également supprimer des questions ou des utilisateurs du système. Ces actions sont réalisées en interagissant avec l'API REST, qui se charge de mettre à jour les données côté serveur.
Ces éléments constituent les principales fonctionnalités de la page de gestion par l'administrateur. Ils permettent à l'administrateur d'interagir avec les données du système de manière pratique et efficace, en offrant des fonctionnalités de création, de mise à jour, de suppression et d'affichage des questions et des utilisateurs.
## Implémentation des sockets pour le jeu de quiz
Dans le fichier `quizz-play.component.ts`, nous avons implémenté les fonctionnalités liées aux sockets pour le jeu de quiz.
### Installation et configuration du module Socket.io dans le projet Angular
Nous avons utilisé le module Socket.io pour la communication en temps réel entre les joueurs et le serveur. Le module a été installé à l'aide de npm et configuré dans le projet Angular.
### Gestion des connexions et des déconnexions des joueurs
Nous avons géré les connexions et les déconnexions des joueurs à l'aide des événements émis par le serveur. Lorsqu'un joueur se connecte, nous émettons l'événement "**player-ready**" au serveur pour indiquer que le joueur est prêt à jouer. Lorsqu'un joueur se déconnecte, nous émettons l'événement "**player-not-ready**" au serveur pour indiquer que le joueur n'est plus prêt.
### Échange de messages en temps réel entre les joueurs et le serveur
Nous avons utilisé les sockets pour échanger des messages en temps réel entre les joueurs et le serveur. Par exemple, lorsque le joueur sélectionne une réponse à une question, nous émettons l'événement "**validate-question**" au serveur avec l'index de la réponse sélectionnée. Le serveur traite ensuite la réponse et met à jour les scores des joueurs.
### Actualisation en direct des classements et des scores des joueurs
Nous avons utilisé les sockets pour actualiser en direct les classements et les scores des joueurs. Lorsqu'un joueur valide sa réponse, le serveur met à jour les scores et envoie les nouvelles données aux joueurs connectés. Dans le fichier `quizz-play.component.html`, nous utilisons des directives \*ngIf et \*ngFor pour afficher en temps réel les classements et les scores des joueurs.
# Angular
\ No newline at end of file
# Babylon JS
# Firebase
## Envoi des données à Firebase
Toutes les méthodes utiles pour envoyer et recevoir des données à Firebase sont dans le fichier *meshprocess/src/app/project.service.ts*. Les méthodes qu'on a dans ce fichier nous est utiles a chaque fois qu'on souhaite actualiser la base de données ou bien envoyer un fichier pour le bon fonctionnement de nos pages ionic.
Sachant que nos images ou les informations à propos de chaque projet et process doivent être stocké pour un meilleur rendu.
## Représentation de chaque projet
Notre base de données Firebase est représenté comme suit:
![Database structure](./img/structure-database.png)
On peut voir ici que la structure dans la base de données est la même que dans notre code. À chaque ajout de projet un id unique est créer dans la base données avec les attributs du projet. Dans notre exemple ci-dessus, ce sera le projet qui contiendra tous les process qui ne vont pas être associé à des projets en particulier. Ce "projet"(qui n'est pas vraiment un au final), ne va pas être afficher sur notre page car ce n'est pas un projet en soit.
À chaque ajout de process dans un projet, il y aura également un id unique par process.
## Stockage des données **obj** de notre process
Les données **obj** vont être stockées dans l'attribut *dataObj* de chaque process. On aura tout le contenu du fichier *.obj* qui va être récupérer à partir de notre backend Django.
## Stockage des images
Dans notre application web, on va devoir stocké une image pour chaque projet et chaque process. Grâce à Firebase on peut les stockés et pouvoir les récupérer facilement avec des lien qui seront stockés dans les attributs des projets et process dans la base de données.
![Image Storage](./img/img-storage.png)
Les images de chaque projet sont stockés dans le dossier *Images*. Et les images de tous les process de chaque projet sont stockés dans les dossier nommé à partir de leur id.
# Fonctionnement
## Structure du frontend
Lorsqu'on accède à l'application, on va tomber sur la page suivante(/home-project):
![Home Project Page](./img/home-project-page.png)
On aura la possibilité d'ajouter différents projets, les supprimer ou bien modifier les process associé au projet. On aura également le bouton en haut à gauche *PROCESS* qui permettra d'importer des process sans créer un projet. Cet page est modifiable dans les fichiers du projet ionic, dans le répertoire *meshprocess/src/app/home-project*.
Chaque page ionic est structuré de cette façon:
![Page structure](./img/page-structure.png)
### Modification du comportement de la page
Comme pour une page html normal, on peut modifier le design de la page dans le **html** et le **scss**. La logique de notre page, se situe dans le fichier typescript qui dans l'exemple ici: *home-project-page.ts*
### Structure d'un projet
Dans ce même dossier ici dans notre page contenant les projets, on a également le fichier *project-model.ts*, qui va contenir ici la classe pour représenter la structure qu'un projet peut avoir:
![Project structure](./img/projet-structure.png)
Cela nous est utile pour pouvoir accéder aux attributs d'un projet plus facilement.
### Structure d'un process
La structure d'un process est lui également situé dans un fichier en particulier *meshprocess/src/app/dashboard/process-model.ts*:
![Process structure](./img/process-structure.png)
## Visualisation des process
### Structure de la page
La page pour visualiser les process de chaque projet est représenter comme ci-dessous:
![Dashboard Page](./img/dashboard-page.png)
Sur cet page on pourra comme pour les projets ajouter un nouveaux process.
### Ajout d'un process
La particularité est qu'on devra communiquer avec notre backend Django pour pouvoir faire la conversion d'un fichier step à importer. Dans notre cas le fichier step sera converti et le backend nous enverra un *json* contenant le contenu du fichier *obj*. Le contenu de ce *json* va être stocké dans l'attribut *dataObj* de chaque process et permettra de l'afficher dans babylonjs
### Affichage d'un objet 3d
Pour afficher le process on utilise la librairie *babylonjs*. *babylonjs* nous permet directement de générer l'aperçu 3d d'une pièce directement à partir du contenu du fichier *obj*. Et puisque dans notre cas, on a directement les données *obj*. On pourra a chaque fois qu'on voudra afficher une pièce en particulier, le générer rapidement.
### Image de chaque process
À chaque ajout d'un nouveau process, la pièce est directement visualisable sur le côté droit de la page. Grâce à la librairie *babylonjs*, on récupère directement une image de la pièce lors de sa génération, et c'est cette image la qui sera stocké dans notre *Firebase*.
### Rendu de la visualisation d'une pièce
Lors qu'on voudra visualiser une pièce, plusieurs informations seront disponibles pour voir les différentes particularités de la pièce:
![Visualisation ](./img/visu-babylon.png)
Ici on a utiliser les GUI de babylonjs pour pouvoir visualiser la pièce de manière optimale pour l'utilisateur.
Documentation/docs/frontend/img/dashboard-page.png

1.35 MiB

Documentation/docs/frontend/img/home-project-page.png

197 KiB

Documentation/docs/frontend/img/img-storage.png

31.6 KiB

Documentation/docs/frontend/img/page-structure.png

5.84 KiB

Documentation/docs/frontend/img/process-structure.png

15.5 KiB

Documentation/docs/frontend/img/projet-structure.png

20.8 KiB

Documentation/docs/frontend/img/structure-database.png

83.1 KiB

Documentation/docs/frontend/img/visu-babylon.png

417 KiB

# Frontend
Cette page contient la documentation concernant la partie frontend du projet.
Le framework [Ionic](https://ionicframework.com/), avec [Angular](https://angular.io/) et
[Tailwind CSS](https://tailwindcss.com/) est utilisé pour le frontend du projet.
## Installation
La marche à suivre pour l'installation et la mise en place du projet est disponible sur le
[gitlab](https://gitedu.hesge.ch/in-code-we-trust/frontend/-/blob/main/README.md#installation).
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment