🎨 READ ME + Remove files
continuous-integration/drone/push Build is failing Details

master
Nathan BOILEAU 2 years ago
parent 1a9d159d64
commit 39193ed5ac

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

@ -1,7 +1,85 @@
# LOL Project
# Projet d'Entity FrameWork et Consomation et Développement de services sur League Of Legends
Ce projet est un travail universitaire réalisé durant la deuxième année de BUT à Clermont-Ferrand. Il correspond au travail demandé dans le cadre du cours regroupant Entity Framework et Consommation et Developpement de Services.
![League Of Legends](./Docs/imageCodeFirst/frontImageReadMe.jpg)
Explique ce qu'on a fait et ce qu'on a pas fait et pourquoi on a priorisé ca plutot que d'autre
Explique ce qu'on a fait et ce qu'on a pas fait et pourquoi on a priorisé ca plutot que d'autre
# :zap: Consomation et Développement de services
Voici l'état des différentes tâches liées à la consommation et au développement de services :
> * :white_check_mark: La mise en place de toutes les opérations CRUD est terminée.
> * :white_check_mark: Une API RESTful a été mise en place en respectant les règles de routage et en utilisant les bons codes de statut.
> * :white_check_mark: La version de l'API a été gérée de manière appropriée.
> * :white_check_mark: Les logs ont été implémentés.
> * :construction: Les tests unitaires sont en cours de réalisation.
> * :construction: La création du client MAUI et sa liaison avec l'API sont en cours de réalisation.
> * :white_check_mark: La liaison avec la base de données est opérationnelle.
> * :white_check_mark: Le filtrage et la pagination des données ont été implémentés.
> * :white_check_mark: Le code est de qualité grâce à l'utilisation de SonarQube.
> * :white_check_mark: L'API a été dockerisée et hébergée sur CodeFirst.
> * :construction: Sécurité
`Note : Le client MAUI n'a pas été réalisé par manque de temps. Les tests unitaires et la sécurité sont réalisés au fur et à mesure de l'avancement du projet.`
---
# :zap: Entity Framework :
Voici l'état des différentes tâches liées à Entity Framework :
> * :white_check_mark: **Exercice 1** : Une base de données a été créée avec une table pour les champions, et des requêtes CRUD ont été implémentées, ainsi que du filtrage et de la pagination. Le client console n'a pas été réalisé pour cet exercice par manque de temps.
> * :white_check_mark: **Exercice 2** : Des tests unitaires ont été écrits et une base de données a été simulée à l'aide de SQLiteInMemory.
> * :white_check_mark: **Exercice 3** : Entity Framework a été déployé et les tests ont été effectués via Code#0.
> * :white_check_mark: **Exercice 4**: Les tables pour les runes et les skins ont été implémentées (sans les relations).
> * :white_check_mark: **Exercice 5** : Une relation OneToMany a été établie entre les champions et les skins.
> * :white_check_mark: **Exercice 6** : Une relation ManyToMany a été établie entre les champions, les rune pages et les runes.
> * :construction: **Exercice 7** : Le mapping entre le modèle et l'entité a été réalisé pour améliorer la qualité du code.
> * :construction: **Exercice 8** : La mise en place du pattern UnitOfWork n'a pas pu être implémentée par manque de temps.
---
### Diagramme d'architechture :
![Diagramme d'architechture](./Docs/imageCodeFirst/DiagrammeArchitecture.png)
#### Partie Client :
La partie client qui n'a pas pu être réalisé dans notre cas, est sensée être constituée du client MAUI et du client Console, qui affichent les ressources et testent l'architecture en utilisant le HTTPDataManager pour effectuer des requêtes à l'API et récupérer des données.
#### DataManager :
Le DataManager utilise l'une des extensions mapper pour convertir les objets DTO en Model, et peut être remplacé par EFDataManager ou StubLib.
#### Partie API :
La partie API reçoit les requêtes et renvoie les objets en conséquence, en utilisant l'EFDataManager pour accéder aux données stockées en base de données. La fluent API permet de définir précisément les attributs de la base de données.
#### Entity Framework :
L'EntityFramework est implémenté avec toutes les classes Entity dérivant du modèle, en utilisant OneToMany et ManyToMany de manière dérivée de celle prévue. Les méthodes CRUD sont implémentées grâce à l'utilisation de l'EFDataManager et le Mapper entre entity et model est requis.
#### Déploiement :
Le projet est déployé avec le projet en conteneur, mais le pattern UnitOfWork n'a pas été abordé.
# :tada: Comment lancer le projet
## 1 - Cloner le dépot
Cloner le dépôt Git en utilisant la commande suivante :
git clone https://codefirst.iut.uca.fr/git/bastien.ollier/LOL.git
## 2 - Configurer le démarrage du projet
> Configurer le projet de démarrage en cliquant sur "Projet de démarrage" à gauche de la flèche verte, puis en sélectionnant l'option "apiLOL".
## 3 - Lancement du projet
Le projet est maintenant prêt à être lancé. Vous pouvez commencer à faire des requêtes sur la base de données via l'API.

@ -1,37 +0,0 @@
using apiLOL;
using apiLOL.Controllers;
using Microsoft.AspNetCore.Mvc;
using StubLib;
namespace TestUnitaire
{
public class TestAPILol
{
[Fact]
public void Test1()
{
}
[Fact]
public void TestPostChampion()
{
// Arrange
var data = new StubData();
var controller = new ControllerChampions(new StubData());
var champDTO = new ChampionDTO("Charles", "Charles est un champion de League of Legends");
// Act
var result = controller.Post(champDTO);
data.ChampionsMgr.AddItem(champDTO.ToModel());
var nbItem = data.ChampionsMgr.GetNbItems();
Task<int> nbItemTask = nbItem;
// Assert
Assert.IsType<OkResult>(result);
// Verify that the champions is added to the stub
Assert.Equal(7, nbItemTask.Result);
}
}
}

@ -1,49 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestUnitaire
{
public class TestEFLol
{
public void TestAddIntoDB()
{
// Arrange
ChampionEntity Zeus = new ChampionEntity
{
Name = "Zeus",
Bio = "Zeus is the king of the gods."
};
// Act
using (var context = new ChampionContext())
{
Console.WriteLine("Adding Zeus to the database...");
context.Champions.Add(Zeus);
context.SaveChanges();
}
// Assert
using (var context = new ChampionContext())
{
var champion = context.Champions.FirstOrDefault(c => c.Name == "Zeus");
if (champion == null)
{
Assert.True(false, "Champion not found in database.");
}
Assert.NotNull(champion);
Assert.Equal("Zeus", champion.Name);
Assert.Equal("Zeus is the king of the gods.", champion.Bio);
}
}
public void TestDeleteFromDB()
{
// Act
}
}
}

@ -1,34 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\apiLOL\apiLOL.csproj" />
</ItemGroup>
</Project>

@ -1,28 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Model\Model.csproj" />
<ProjectReference Include="..\..\StubLib\StubLib.csproj" />
</ItemGroup>
<ItemGroup>
<None Remove="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
</ItemGroup>
</Project>

@ -1,338 +0,0 @@
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Model;
using StubLib;
using static System.Console;
namespace ConsoleTests
{
static class Program
{
static IDataManager dataManager = null!;
static async Task Main(string[] args)
{
try
{
using var servicesProvider = new ServiceCollection()
.AddSingleton<IDataManager, StubData>()
.BuildServiceProvider();
dataManager = servicesProvider.GetRequiredService<IDataManager>();
await DisplayMainMenu();
Console.ReadLine();
}
catch (Exception ex)
{
Debug.WriteLine(ex, "Stopped program because of exception");
throw;
}
}
public static async Task DisplayMainMenu()
{
Dictionary<int, string> choices = new Dictionary<int, string>()
{
[1] = "1- Manage Champions",
[2] = "2- Manage Skins",
[3] = "3- Manage Runes",
[4] = "4- Manage Rune Pages",
[99] = "99- Quit"
};
while(true)
{
int input = DisplayAMenu(choices);
switch(input)
{
case 1:
await DisplayChampionsMenu();
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 99:
WriteLine("Bye bye!");
return;
default:
break;
}
}
}
private static int DisplayAMenu(Dictionary<int, string> choices)
{
int input=-1;
while(true)
{
WriteLine("What is your choice?");
WriteLine("--------------------");
foreach(var choice in choices.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value))
{
WriteLine(choice);
}
if(!int.TryParse(ReadLine(), out input) || input == -1)
{
WriteLine("I do not understand what your choice is. Please try again.");
continue;
}
break;
}
WriteLine($"You have chosen: {choices[input]}");
WriteLine();
return input;
}
public static async Task DisplayChampionsMenu()
{
Dictionary<int, string> choices = new Dictionary<int, string>()
{
[0] = "0- Get number of champions",
[1] = "1- Get champions",
[2] = "2- Find champions by name",
[3] = "3- Find champions by characteristic",
[4] = "4- Find champions by class",
[5] = "5- Find champions by skill",
[6] = "6- Add new champion",
[7] = "7- Delete a champion",
[8] = "8- Update a champion",
};
int input = DisplayAMenu(choices);
switch(input)
{
case 0:
int nb = await dataManager.ChampionsMgr.GetNbItems();
WriteLine($"There are {nb} champions");
WriteLine("**********************");
break;
case 1:
{
int index = ReadAnInt("Please enter the page index");
int count = ReadAnInt("Please enter the number of elements to display");
WriteLine($"{count} champions of page {index+1}");
var champions = await dataManager.ChampionsMgr.GetItems(index, count, nameof(Champion.Name));
foreach(var champion in champions)
{
WriteLine($"\t{champion}");
}
WriteLine("**********************");
}
break;
case 2:
{
string substring = ReadAString("Please enter the substring to look for in the name of a champion");
int index = ReadAnInt("Please enter the page index");
int count = ReadAnInt("Please enter the number of elements to display");
var champions = await dataManager.ChampionsMgr.GetItemsByName(substring, index, count, nameof(Champion.Name));
foreach(var champion in champions)
{
WriteLine($"\t{champion}");
}
WriteLine("**********************");
}
break;
case 3:
{
string substring = ReadAString("Please enter the substring to look for in the characteristics of champions");
int index = ReadAnInt("Please enter the page index");
int count = ReadAnInt("Please enter the number of elements to display");
var champions = await dataManager.ChampionsMgr.GetItemsByCharacteristic(substring, index, count, nameof(Champion.Name));
foreach(var champion in champions)
{
WriteLine($"\t{champion}");
}
WriteLine("**********************");
}
break;
case 4:
{
ChampionClass championClass = ReadAnEnum<ChampionClass>($"Please enter the champion class (possible values are: {Enum.GetNames<ChampionClass>().Aggregate("", (name, chaine) => $"{chaine} {name}")}):");
int index = ReadAnInt("Please enter the page index");
int count = ReadAnInt("Please enter the number of elements to display");
var champions = await dataManager.ChampionsMgr.GetItemsByClass(championClass, index, count, nameof(Champion.Name));
foreach(var champion in champions)
{
WriteLine($"\t{champion}");
}
WriteLine("**********************");
}
break;
case 5:
{
string substring = ReadAString("Please enter the substring to look for in the skills of champions");
int index = ReadAnInt("Please enter the page index");
int count = ReadAnInt("Please enter the number of elements to display");
var champions = await dataManager.ChampionsMgr.GetItemsBySkill(substring, index, count, nameof(Champion.Name));
foreach(var champion in champions)
{
WriteLine($"\t{champion}");
}
WriteLine("**********************");
}
break;
case 6:
{
WriteLine("You are going to create a new champion.");
string name = ReadAString("Please enter the champion name:");
ChampionClass championClass = ReadAnEnum<ChampionClass>($"Please enter the champion class (possible values are: {Enum.GetNames<ChampionClass>().Aggregate("", (name, chaine) => $"{chaine} {name}")}):");
string bio = ReadAString("Please enter the champion bio:");
Champion champion = new Champion(name, championClass, bio: bio);
DisplayCreationChampionMenu(champion);
_ = await dataManager.ChampionsMgr.AddItem(champion);
}
break;
case 7:
{
WriteLine("You are going to delete a champion.");
string name = ReadAString("Please enter the champion name:");
var somechampions = await dataManager.ChampionsMgr.GetItemsByName(name, 0, 10, nameof(Champion.Name));
var someChampionNames = somechampions.Select(c => c!.Name);
var someChampionNamesAsOneString = someChampionNames.Aggregate("", (name, chaine) => $"{chaine} {name}");
string champName = ReadAStringAmongPossibleValues($"Who do you want to delete among these champions? (type \"Cancel\" to ... cancel) {someChampionNamesAsOneString}",
someChampionNames.ToArray());
if(champName != "Cancel")
{
await dataManager.ChampionsMgr.DeleteItem(somechampions.Single(c => c!.Name == champName));
}
}
break;
case 8:
{
WriteLine("You are going to update a champion.");
string name = ReadAString("Please enter the champion name:");
var somechampions = await dataManager.ChampionsMgr.GetItemsByName(name, 0, 10, nameof(Champion.Name));
var someChampionNames = somechampions.Select(c => c!.Name);
var someChampionNamesAsOneString = someChampionNames.Aggregate("", (name, chaine) => $"{chaine} {name}");
string champName = ReadAStringAmongPossibleValues($"Who do you want to update among these champions? (type \"Cancel\" to ... cancel) {someChampionNamesAsOneString}",
someChampionNames.ToArray());
if(champName == "Cancel") break;
ChampionClass championClass = ReadAnEnum<ChampionClass>($"Please enter the champion class (possible values are: {Enum.GetNames<ChampionClass>().Aggregate("", (name, chaine) => $"{chaine} {name}")}):");
string bio = ReadAString("Please enter the champion bio:");
Champion champion = new Champion(champName, championClass, bio: bio);
DisplayCreationChampionMenu(champion);
await dataManager.ChampionsMgr.UpdateItem(somechampions.Single(c => c!.Name == champName), champion);
}
break;
default:
break;
}
}
public static void DisplayCreationChampionMenu(Champion champion)
{
Dictionary<int, string> choices = new Dictionary<int, string>()
{
[1] = "1- Add a skill",
[2] = "2- Add a skin",
[3] = "3- Add a characteristic",
[99] = "99- Finish"
};
while(true)
{
int input = DisplayAMenu(choices);
switch(input)
{
case 1:
string skillName = ReadAString("Please enter the skill name:");
SkillType skillType = ReadAnEnum<SkillType>($"Please enter the skill type (possible values are: {Enum.GetNames<SkillType>().Aggregate("", (name, chaine) => $"{chaine} {name}")}):");
string skillDescription = ReadAString("Please enter the skill description:");
Skill skill = new Skill(skillName, skillType, skillDescription);
champion.AddSkill(skill);
break;
case 2:
string skinName = ReadAString("Please enter the skin name:");
string skinDescription = ReadAString("Please enter the skin description:");
float skinPrice = ReadAFloat("Please enter the price of this skin:");
Skin skin = new Skin(skinName, champion, skinPrice, description: skinDescription);
break;
case 3:
string characteristic = ReadAString("Please enter the characteristic:");
int value = ReadAnInt("Please enter the value associated to this characteristic:");
champion.AddCharacteristics(Tuple.Create(characteristic, value));
break;
case 99:
return;
default:
break;
}
}
}
private static int ReadAnInt(string message)
{
while(true)
{
WriteLine(message);
if(!int.TryParse(ReadLine(), out int result))
{
continue;
}
return result;
}
}
private static float ReadAFloat(string message)
{
while(true)
{
WriteLine(message);
if(!float.TryParse(ReadLine(), out float result))
{
continue;
}
return result;
}
}
private static string ReadAString(string message)
{
while(true)
{
WriteLine(message);
string? line = ReadLine();
if(line == null)
{
continue;
}
return line!;
}
}
private static TEnum ReadAnEnum<TEnum>(string message) where TEnum :struct
{
while(true)
{
WriteLine(message);
if(!Enum.TryParse<TEnum>(ReadLine(), out TEnum result))
{
continue;
}
return result;
}
}
private static string ReadAStringAmongPossibleValues(string message, params string[] possibleValues)
{
while(true)
{
WriteLine(message);
string? result = ReadLine();
if(result == null) continue;
if(result != "Cancel" && !possibleValues.Contains(result!)) continue;
return result!;
}
}
}
}
Loading…
Cancel
Save