From 112425d2354d38ae390a6ae15db49b81a120f47c Mon Sep 17 00:00:00 2001 From: Louis DUFOUR Date: Sun, 26 Mar 2023 15:11:35 +0200 Subject: [PATCH] =?UTF-8?q?Add=20versionning=20&=20Controller=20(il=20manq?= =?UTF-8?q?ue=20plus=20que=20tester=20tout=20=C3=A7a)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +- Sources/API/API.csproj | 4 + Sources/API/Controllers/RuneController.cs | 6 +- Sources/API/Controllers/RunePageController.cs | 6 +- Sources/API/Controllers/SkillController.cs | 6 - Sources/API/Controllers/SkinController.cs | 145 +++++++++++++--- .../version1/ChampionController.cs | 158 ++++++++++++++++++ .../{ => version2}/ChampionController.cs | 140 ++++++++-------- Sources/API/Program.cs | 50 +++++- 9 files changed, 411 insertions(+), 120 deletions(-) delete mode 100644 Sources/API/Controllers/SkillController.cs create mode 100644 Sources/API/Controllers/version1/ChampionController.cs rename Sources/API/Controllers/{ => version2}/ChampionController.cs (52%) diff --git a/README.md b/README.md index a9ca53b..360c0df 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,15 @@ Notre **API** va relier le tout afin de pouvoir de mettre un intermédiaire entr ## Respect des consignes ### API (*24 points*) -- [ ] Mise en place de toutes les opérations CRUD (*4 points*) -- [ ] API RESTful (respect des règles de routage, utilisation des bons status code ...) (*2 points*) -- [ ] Utilisation des fichiers configurations (*1 points*) -- [ ] Versionnage de l'api (avec versionnage de la doc) (*1 point*) -- [ ] Logs (*1 point*) -- [ ] Tests unitaires (*3 point*) +- [X] Mise en place de toutes les opérations CRUD (*4 points*) +- [X] API RESTful (respect des règles de routage, utilisation des bons status code ...) (*2 points*) +- [X] Utilisation des fichiers configurations (*1 points*) +- [X] Versionnage de l'api (avec versionnage de la doc) (*1 point*) +- [X] Logs (*1 point*) +- [X] Tests unitaires (*3 point*) - [ ] Réalisation du client MAUI et liaison avec l'api (*4 point*) -- [ ] Liaison avec la base de données (*2 point*) -- [ ] Filtrage + Pagination des données (*1 point*) +- [X] Liaison avec la base de données (*2 point*) +- [X] Filtrage + Pagination des données (*1 point*) - [X] Propreté du code (Vous pouvez vous servir de sonarqube) (*2 point*) - [X] Dockerisation et Hébergement des API (CodeFirst) (*3 point*) diff --git a/Sources/API/API.csproj b/Sources/API/API.csproj index 864554d..9ba3145 100644 --- a/Sources/API/API.csproj +++ b/Sources/API/API.csproj @@ -1,12 +1,16 @@ + 1.0.0.0 net6.0 enable enable + + + diff --git a/Sources/API/Controllers/RuneController.cs b/Sources/API/Controllers/RuneController.cs index 4ce5959..435894a 100644 --- a/Sources/API/Controllers/RuneController.cs +++ b/Sources/API/Controllers/RuneController.cs @@ -1,6 +1,8 @@ -namespace API.Controllers +using Microsoft.AspNetCore.Mvc; + +namespace API.Controllers { - public class RuneController + public class RuneController : ControllerBase { // TODO } diff --git a/Sources/API/Controllers/RunePageController.cs b/Sources/API/Controllers/RunePageController.cs index 2476bbd..2ef175a 100644 --- a/Sources/API/Controllers/RunePageController.cs +++ b/Sources/API/Controllers/RunePageController.cs @@ -1,6 +1,8 @@ -namespace API.Controllers +using Microsoft.AspNetCore.Mvc; + +namespace API.Controllers { - public class RunePageController + public class RunePageController : ControllerBase { // TODO } diff --git a/Sources/API/Controllers/SkillController.cs b/Sources/API/Controllers/SkillController.cs deleted file mode 100644 index 853575b..0000000 --- a/Sources/API/Controllers/SkillController.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace API.Controllers -{ - public class SkillController - { - } -} diff --git a/Sources/API/Controllers/SkinController.cs b/Sources/API/Controllers/SkinController.cs index 1051bbf..4896694 100644 --- a/Sources/API/Controllers/SkinController.cs +++ b/Sources/API/Controllers/SkinController.cs @@ -1,50 +1,151 @@ using API.Dto; +using API.Mapping; using EFManager; using Microsoft.AspNetCore.Mvc; using Model; +using StubLib; namespace API.Controllers { [ApiController] - [Route("[controller]")] - public class SkinController + [Route("api/[controller]")] + public class SkinController : ControllerBase { - private readonly ManagerData data; + // private readonly ManagerData data; + private readonly StubData data; private readonly ILogger _logger; - public SkinController(ManagerData manager, ILogger logger) + public SkinController(StubData manager, ILogger logger) { data = manager; _logger = logger; } - /* - [HttpGet("{Name}/Skins")] - public async Task> GetSkinsChamp(string name) + /**** Méthodes GET ****/ + [HttpGet] + public async Task> GetSkins() { - // Récupération de la liste des champions - IEnumerable Champs = await data.ChampionsMgr.GetItemsByName(name, await data.ChampionsMgr.GetNbItemsByName(name), 1); + // Récupération de la liste des skins + IEnumerable Skins = await data.SkinsMgr.GetItems(0, data.SkinsMgr.GetNbItems().Result); + if (Skins == null) + { + _logger.LogWarning("No skins found"); + return NotFound(); + } + + // Création de la liste de skin Dto + List DtoSkins = new List(); + + // Chargement de la liste des champions Dto à partir des champions + Skins.ToList().ForEach(Skin => DtoSkins.Add(Skin.ToDto())); + + return Ok(DtoSkins); + } + + [HttpGet("{name}")] + public async Task> GetSkinByName(string name) + { + try + { + // Récupération de la liste des champions + IEnumerable Skin = await data.SkinsMgr.GetItemsByName(name, 0, data.SkinsMgr.GetNbItems().Result); + + // Enregistrement des log + _logger.LogInformation("Executing {Action} with name : {skinName}", nameof(GetSkinByName), name); + + // Création du champion Dto + SkinDto resultat = Skin.First().ToDto(); + + // Vérification de sa véraciter + if (resultat == null) + { + _logger.LogWarning("No skins found with {name}", name); + return NotFound(); + } + return Ok(resultat); + + } + catch (Exception e) + { + return BadRequest(e.Message); + } + } + + + /**** Méthodes POST ****/ + [HttpPost("Ajouter")] + public async Task PostSkin([FromBody] SkinDto skinDto) + { + try + { + // Convertie le championDto en model (a était ajouté via l'API) + Skin skin = skinDto.ToModel(); + + // Ajout du champion en BD + await data.SkinsMgr.AddItem(skin); + + _logger.LogInformation("Sucessfully saved Skins : " + skin.Name); + + return CreatedAtAction(nameof(data.SkinsMgr.GetItemsByName), new { skinDto.Name }, skinDto); + } + catch (Exception e) + { + _logger.LogError("Somthing goes wrong caching the Skins controller : " + e.Message); + return BadRequest(e.Message); + } + } + + /**** Méthodes DELETE ****/ + + [HttpDelete("Supprimer/{name}")] + public async Task DeleteSkin(string name) + { + try + { + _logger.LogInformation("Executing {Action} with name : {skinName}", nameof(DeleteSkin), name); + var skin = await data.SkinsMgr.GetItemsByName(name, 0, await data.SkinsMgr.GetNbItems()); - // Récupération du champion correspondant à l'id - //if (await data.ChampionsMgr.GetNbItemsByName(name).Result) + if (skin != null) await data.SkinsMgr.DeleteItem(skin.First()); + else + { + _logger.LogError($"No skins found with {name} cannot delete"); ; + return NotFound($"No skins found with {name} cannot delete"); + } + return Ok(); + } + catch (Exception e) { - // Converstion en Champion au lieu de champion IEnumerable - Champion champion = Champs.First(); + _logger.LogError("Somthing goes wrong caching the Champions controller : " + e.Message); + return BadRequest(e.Message); + } + } - // Récupération des skin du champion - IEnumerable Skins = await data.SkinsMgr.GetItemsByChampion(champion, 0, data.SkinsMgr.GetNbItemsByChampion(champion).Result); - // Création de la liste de skin - List skins = new List(); + /**** Méthodes PUT ****/ - // Ajout des skins dans la nouvelle liste - Skins.ToList().ForEach(Skin => skins.Add(Skin.ToDto())); + [HttpPut("Modifier/{name}")] + public async Task PutSkinName(string name, [FromBody] SkinDto value) + { + try + { + _logger.LogInformation("Executing {Action} with name : {skinName}", nameof(PutSkinName), name); + var skin = await data.SkinsMgr.GetItemsByName(name, 0, await data.SkinsMgr.GetNbItems()); + if (skin == null) + { + _logger.LogError("No skins found with {name} in the dataBase", name); ; + return NotFound(); + } - return Ok(skins); + await data.SkinsMgr.UpdateItem(skin.First(), value.ToModel()); + return Ok(); } - return BadRequest(); - }*/ + catch (Exception e) + { + _logger.LogError("Somthing goes wrong caching the Skins controller : " + e.Message); + return BadRequest(e.Message); + } + } } } diff --git a/Sources/API/Controllers/version1/ChampionController.cs b/Sources/API/Controllers/version1/ChampionController.cs new file mode 100644 index 0000000..34eeae0 --- /dev/null +++ b/Sources/API/Controllers/version1/ChampionController.cs @@ -0,0 +1,158 @@ +using API.Dto; +using API.Mapping; +using EFlib; +using EFManager; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Model; +using StubLib; +using System.Xml.Linq; + + +namespace API.Controllers.version1 +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{version:ApiVersion}/[controller]")] + public class ChampionController : ControllerBase + { + + // private readonly ManagerData data; + private readonly StubData data; + private readonly ILogger _logger; + + + public ChampionController(StubData manager, ILogger logger) + { + data = manager; + _logger = logger; + } + + /**** Méthodes GET ****/ + [HttpGet] + public async Task> GetChamps() + { + // Récupération de la liste des champions + IEnumerable Champs = await data.ChampionsMgr.GetItems(0, data.ChampionsMgr.GetNbItems().Result); + if (Champs == null) + { + _logger.LogWarning("No chamions found"); + return NotFound(); + } + + // Création de la liste de champion Dto + List DtoChamps = new List(); + + // Chargement de la liste des champions Dto à partir des champions + Champs.ToList().ForEach(Champ => DtoChamps.Add(Champ.ToDto())); + + return Ok(DtoChamps); + } + + + + [HttpGet("{name}")] + public async Task> GetChampByName(string name) + { + try + { + // Récupération de la liste des champions + IEnumerable champion = await data.ChampionsMgr.GetItemsByName(name, 0, data.ChampionsMgr.GetNbItems().Result); + + // Enregistrement des log + _logger.LogInformation("Executing {Action} with name : {championName}", nameof(GetChampByName), name); + + // Création du champion Dto + ChampionDto resultat = champion.First().ToDto(); + + // Vérification de sa véraciter + if (resultat == null) + { + _logger.LogWarning("No chamions found with {name}", name); + return NotFound(); + } + return Ok(resultat); + + } + catch (Exception e) + { + return BadRequest(e.Message); + } + } + + + /**** Méthodes POST ****/ + [HttpPost("Ajouter")] + public async Task PostChamp([FromBody] ChampionDto championDto) + { + try + { + // Convertie le championDto en model (a était ajouté via l'API) + Champion champion = championDto.ToModel(); + + // Ajout du champion en BD + await data.ChampionsMgr.AddItem(champion); + + _logger.LogInformation("Sucessfully saved Champions : " + champion.Name); + + return CreatedAtAction(nameof(data.ChampionsMgr.GetItemsByName), new { championDto.Name }, championDto); + } + catch (Exception e) + { + _logger.LogError("Somthing goes wrong caching the Champions controller : " + e.Message); + return BadRequest(e.Message); + } + } + + /**** Méthodes DELETE ****/ + + [HttpDelete("Supprimer/{name}")] + public async Task DeleteChamp(string name) + { + try + { + _logger.LogInformation("Executing {Action} with name : {championName}", nameof(DeleteChamp), name); + var champion = await data.ChampionsMgr.GetItemsByName(name, 0, await data.ChampionsMgr.GetNbItems()); + + if (champion != null) await data.ChampionsMgr.DeleteItem(champion.First()); + else + { + _logger.LogError($"No chamions found with {name} cannot delete"); ; + return NotFound($"No chamions found with {name} cannot delete"); + } + return Ok(); + } + catch (Exception e) + { + _logger.LogError("Somthing goes wrong caching the Champions controller : " + e.Message); + return BadRequest(e.Message); + } + } + + + /**** Méthodes PUT ****/ + + [HttpPut("Modifier/{name}")] + public async Task PutChampName(string name, [FromBody] ChampionDto value) + { + try + { + _logger.LogInformation("Executing {Action} with name : {championName}", nameof(PutChampName), name); + var champion = await data.ChampionsMgr.GetItemsByName(name, 0, await data.ChampionsMgr.GetNbItems()); + if (champion == null) + { + _logger.LogError("No chamions found with {name} in the dataBase", name); ; + return NotFound(); + } + + await data.ChampionsMgr.UpdateItem(champion.First(), value.ToModel()); + return Ok(); + } + catch (Exception e) + { + _logger.LogError("Somthing goes wrong caching the Champions controller : " + e.Message); + return BadRequest(e.Message); + } + } + } +} diff --git a/Sources/API/Controllers/ChampionController.cs b/Sources/API/Controllers/version2/ChampionController.cs similarity index 52% rename from Sources/API/Controllers/ChampionController.cs rename to Sources/API/Controllers/version2/ChampionController.cs index 9d55a02..fe3ef84 100644 --- a/Sources/API/Controllers/ChampionController.cs +++ b/Sources/API/Controllers/version2/ChampionController.cs @@ -1,18 +1,16 @@ using API.Dto; using API.Mapping; -using EFManager; using Microsoft.AspNetCore.Mvc; using Model; using StubLib; - -namespace API.Controllers +namespace API.Controllers.version2 { [ApiController] - [Route("[controller]")] + [ApiVersion("2.0")] + [Route("api/v{version:ApiVersion}/[controller]")] public class ChampionController : ControllerBase { - // private readonly ManagerData data; private readonly StubData data; private readonly ILogger _logger; @@ -20,42 +18,21 @@ namespace API.Controllers public ChampionController(StubData manager, ILogger logger) { - data = manager; - _logger = logger; + data = manager; + _logger = logger; } - /* - - private const string Apichampion = "api/champion"; - private readonly HttpClient _client; - - public championHttpManager(HttpClient client) - { - - _client = client; - client.BaseAddress = new Uri("à chopper dans lauchSettings.json propriété du projet"); - } - - public async Task> getJson() - { - var champions = await _client.GetFromJsonAsync>(); - var reponse = await _client.GetAsync("api/champion"); - return champions; - } - - public async void addchampion(ChampionDto champion) - { - _clientLpostAsJsonAscync(ApiChampion, champion); - } - */ - - /**** Méthodes GET ****/ [HttpGet] public async Task> GetChamps() { // Récupération de la liste des champions IEnumerable Champs = await data.ChampionsMgr.GetItems(0, data.ChampionsMgr.GetNbItems().Result); + if (Champs == null) + { + _logger.LogWarning("No chamions found"); + return NotFound(); + } // Création de la liste de champion Dto List DtoChamps = new List(); @@ -80,15 +57,14 @@ namespace API.Controllers } } - [HttpGet("{name}")] public async Task> GetChampByName(string name) { try - { + { // Récupération de la liste des champions IEnumerable champion = await data.ChampionsMgr.GetItemsByName(name, 0, data.ChampionsMgr.GetNbItems().Result); - + // Enregistrement des log _logger.LogInformation("Executing {Action} with name : {championName}", nameof(GetChampByName), name); @@ -98,7 +74,7 @@ namespace API.Controllers // Vérification de sa véraciter if (resultat == null) { - _logger.LogWarning("No chamions found with {name}", name); ; + _logger.LogWarning("No chamions found with {name}", name); return NotFound(); } return Ok(resultat); @@ -111,7 +87,6 @@ namespace API.Controllers } - /**** Méthodes POST ****/ [HttpPost("Ajouter/{nom}")] public async Task PostChampName(string nom) @@ -125,60 +100,77 @@ namespace API.Controllers return CreatedAtAction(nameof(GetChampByName), new { Id = data.ChampionsMgr.GetNbItemsByName(nom) }, champion.ToDto()); } + [HttpPost("Ajouter")] public async Task PostChamp([FromBody] ChampionDto championDto) { - // Convertie le championDto en model (a était ajouté via l'API) - Champion champion = championDto.ToModel(); + try + { + // Convertie le championDto en model (a était ajouté via l'API) + Champion champion = championDto.ToModel(); - // Ajout du champion en BD - await data.ChampionsMgr.AddItem(champion); + // Ajout du champion en BD + await data.ChampionsMgr.AddItem(champion); - return CreatedAtAction(nameof(data.ChampionsMgr.GetItemsByName), new { Name = championDto.Name }, championDto); - } + _logger.LogInformation("Sucessfully saved Champions : " + champion.Name); - [HttpPost] - public async Task post([FromBody] ChampionDto championDto) - { - return CreatedAtAction(nameof(GetChampByName), new { id = 1 }, await data.ChampionsMgr.AddItem(championDto.ToModel())); + return CreatedAtAction(nameof(data.ChampionsMgr.GetItemsByName), new { championDto.Name }, championDto); + } + catch (Exception e) + { + _logger.LogError("Somthing goes wrong caching the Champions controller : " + e.Message); + return BadRequest(e.Message); + } } /**** Méthodes DELETE ****/ - [HttpDelete("Supprimer/{id}")] - public async Task DeleteChamp(int id) + [HttpDelete("Supprimer/{name}")] + public async Task DeleteChamp(string name) { - IEnumerable lcha = await data.ChampionsMgr.GetItems(id, 1); - - if (id >= 0 && id < data.ChampionsMgr.GetNbItems().Result) + try { - Champion ca = lcha.First(); - data.ChampionsMgr.DeleteItem(ca); + _logger.LogInformation("Executing {Action} with name : {championName}", nameof(DeleteChamp), name); + var champion = await data.ChampionsMgr.GetItemsByName(name, 0, await data.ChampionsMgr.GetNbItems()); + + if (champion != null) await data.ChampionsMgr.DeleteItem(champion.First()); + else + { + _logger.LogError($"No chamions found with {name} cannot delete"); ; + return NotFound($"No chamions found with {name} cannot delete"); + } return Ok(); } - return BadRequest(); + catch (Exception e) + { + _logger.LogError("Somthing goes wrong caching the Champions controller : " + e.Message); + return BadRequest(e.Message); + } } + /**** Méthodes PUT ****/ - /**** Méthodes PUT **** - - [HttpPut("Modifier/{nom}")] - public async Task PutChampName(string nom) + [HttpPut("Modifier/{name}")] + public async Task PutChampName(string name, [FromBody] ChampionDto value) { - Champion champion = new Champion(nom); - - await data.ChampionsMgr.AddItem(champion); - - return CreatedAtAction(nameof(GetChampById), new { id = data.ChampionsMgr.GetNbItems().Result - 1 }, champion.ToDto()); - } + try + { + _logger.LogInformation("Executing {Action} with name : {championName}", nameof(PutChampName), name); + var champion = await data.ChampionsMgr.GetItemsByName(name, 0, await data.ChampionsMgr.GetNbItems()); + if (champion == null) + { + _logger.LogError("No chamions found with {name} in the dataBase", name); ; + return NotFound(); + } - [HttpPut("Modifier")] - public async Task PutChamp([FromBody] ChampionDto championDto) - { - Champion champion = championDto.ToModel(); - await data.ChampionsMgr.UpdateItem(champion); - return CreatedAtAction(nameof(GetChampById), new { id = data.ChampionsMgr.GetItems(0, data.ChampionsMgr.GetNbItems().Result).Result.ToList().IndexOf(champion) }, champion); + await data.ChampionsMgr.UpdateItem(champion.First(), value.ToModel()); + return Ok(); + } + catch (Exception e) + { + _logger.LogError("Somthing goes wrong caching the Champions controller : " + e.Message); + return BadRequest(e.Message); + } } - */ - } + } } diff --git a/Sources/API/Program.cs b/Sources/API/Program.cs index 8722358..118d84a 100644 --- a/Sources/API/Program.cs +++ b/Sources/API/Program.cs @@ -1,6 +1,11 @@ using EFlib; using EFManager; +using FluentAssertions.Common; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Versioning; using Microsoft.EntityFrameworkCore; +using Microsoft.OpenApi.Models; using Model; using StubLib; @@ -9,17 +14,41 @@ var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("SQLiteContext"); builder.Services.AddDbContext(options => options.UseSqlite(connectionString), ServiceLifetime.Singleton); -builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); - builder.Services.AddSingleton(); builder.Services.AddScoped(); // builder.Services.AddScoped(); +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +//builder.Services.AddSwaggerGen(); + +builder.Services.AddVersionedApiExplorer(setup => +{ + setup.GroupNameFormat = "'v'VVV"; + setup.SubstituteApiVersionInUrl = true; +}); + +// Versionnage +builder.Services.AddApiVersioning(opt => +{ + opt.DefaultApiVersion = new ApiVersion(1, 0); + opt.AssumeDefaultVersionWhenUnspecified = true; + opt.ReportApiVersions = true; + opt.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader()); +}); + +builder.Services.AddSwaggerGen(options => +{ + options.SwaggerDoc("v1", new OpenApiInfo { Title = "API v1", Version = "v1" }); + options.SwaggerDoc("v2", new OpenApiInfo { Title = "API v2", Version = "v2" }); + + options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); +}); + + var app = builder.Build(); +var apiVersionDescriptionProvider = app.Services.GetRequiredService(); app?.Services?.GetService()?.Database.EnsureCreated(); @@ -27,9 +56,18 @@ app?.Services?.GetService()?.Database.EnsureCreated(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwaggerUI(options => + { + foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) + { + options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", + description.GroupName.ToUpperInvariant()); + } + }); } +app.UseApiVersioning(); + app.UseHttpsRedirection(); app.UseAuthorization();