diff --git a/.drone.yml b/.drone.yml index b3304ee..bd1fa7c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,6 +15,14 @@ steps: - dotnet build HeartTrack.sln -c Release --no-restore - dotnet publish HeartTrack.sln -c Release --no-restore -o CI_PROJECT_DIR/build/release + - name: tests + image: mcr.microsoft.com/dotnet/sdk:8.0 + commands: + - cd src/ + - dotnet restore HeartTrack.sln + - dotnet test HeartTrack.sln --no-restore + depends_on: [build] + - name: code-analysis image: hub.codefirst.iut.uca.fr/marc.chevaldonne/codefirst-dronesonarplugin-dotnet8 secrets: [ SECRET_SONAR_LOGIN ] @@ -33,8 +41,54 @@ steps: - reportgenerator -reports:"**/coverage.cobertura.xml" -reporttypes:SonarQube -targetdir:"coveragereport" - dotnet publish HeartTrack.sln -c Release --no-restore -o $CI_PROJECT_DIR/build/release - dotnet sonarscanner end /d:sonar.login=$${PLUGIN_SONAR_TOKEN} - depends_on: [ build ] + depends_on: [ tests ] + - name: swagger + image: mcr.microsoft.com/dotnet/sdk:8.0 + failure: ignore + volumes: + - name: docs + path: /docs + commands: + - cd src/ + - dotnet restore HeartTrack.sln + - cd HeartTrack + - dotnet new tool-manifest + - dotnet tool install -g --version 6.5.0 Swashbuckle.AspNetCore.Cli + - cd ../ + - dotnet build HeartTrack.sln -c Release --no-restore + - dotnet publish HeartTrack.sln -c Release --no-restore -o CI_PROJECT_DIR/build/release + - export PATH="$PATH:/root/.dotnet/tools" + - swagger tofile --output /docs/swagger.json HeartTrack/bin/Release/net8.0/HeartTrack.dll v1 + - name: generate-and-deploy-docs + image: hub.codefirst.iut.uca.fr/maxime.batista/codefirst-docdeployer + failure: ignore + commands: + - /entrypoint.sh -l docs/doxygen -t doxygen + when: + event: + - push + depends_on: [ build ] + +volumes: +- name: docs + temp: {} + +--- + +kind: pipeline +type: docker +name: HeartTrack-API-CD + +trigger: + event: + - push +steps: + - name: hadolint + image: hadolint/hadolint:latest-alpine + commands: + - cd src/HeartTrackAPI + - hadolint Dockerfile - name: docker-build-and-push image: plugins/docker settings: @@ -47,24 +101,65 @@ steps: password: from_secret: SECRET_REGISTRY_PASSWORD depends_on: [ build ] - - - name: deploy-container + + # database container stub + - name: deploy-container-stub image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest environment: + CODEFIRST_CLIENTDRONE_ENV_TYPE: STUB IMAGENAME: hub.codefirst.iut.uca.fr/david.d_almeida/api:latest - CONTAINERNAME: api - CODEFIRST_CLIENTDRONE_ENV_PORT: 8080 - ADMINS: davidd_almeida,kevinmonteiro,antoineperederii,paullevrault,antoinepinagot,nicolas.raymond + CONTAINERNAME: heart_stub COMMAND: create OVERWRITE: true depends_on: [ docker-build-and-push ] - - - name: generate-and-deploy-docs - image: hub.codefirst.iut.uca.fr/maxime.batista/codefirst-docdeployer - failure: ignore - commands: - - /entrypoint.sh -l docs/doxygen -t doxygen - when: - event: - - push - depends_on: [ build ] \ No newline at end of file + + # - name: deploy-container + # image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest + # environment: + # IMAGENAME: hub.codefirst.iut.uca.fr/david.d_almeida/api:latest + # CONTAINERNAME: heart_api + # CODEFIRST_CLIENTDRONE_ENV_TYPE: API + # CODEFIRST_CLIENTDRONE_ENV_PORT: 8080 + # ADMINS: davidd_almeida,kevinmonteiro,antoineperederii,paullevrault,antoinepinagot,nicolas.raymond + # COMMAND: create + # OVERWRITE: true + # depends_on: [ docker-build-and-push, deploy-container-stub ] + + + + # database container deployment + - name: deploy-container-mysql + image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest + environment: + IMAGENAME: mariadb:10 + CONTAINERNAME: mysql + COMMAND: create + # OVERWRITE: false + PRIVATE: true + CODEFIRST_CLIENTDRONE_ENV_MARIADB_ROOT_PASSWORD: + from_secret: db_root_password + CODEFIRST_CLIENTDRONE_ENV_MARIADB_DATABASE: + from_secret: db_database + CODEFIRST_CLIENTDRONE_ENV_MARIADB_USER: + from_secret: db_user + CODEFIRST_CLIENTDRONE_ENV_MARIADB_PASSWORD: + from_secret: db_password + + # database container bdd + - name: deploy-container-bdd + image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest + environment: + CODEFIRST_CLIENTDRONE_ENV_TYPE: BDD + CODEFIRST_CLIENTDRONE_ENV_HOST: dadalmeida-mysql + CODEFIRST_CLIENTDRONE_ENV_PORT: 3306 + CODEFIRST_CLIENTDRONE_ENV_DATABASE: + from_secret: db_database + CODEFIRST_CLIENTDRONE_ENV_USERNAME: + from_secret: db_user + CODEFIRST_CLIENTDRONE_ENV_PASSWORD: + from_secret: db_password + IMAGENAME: hub.codefirst.iut.uca.fr/david.d_almeida/api:latest:latest + CONTAINERNAME: heart_api + COMMAND: create + OVERWRITE: true + depends_on: [deploy-container-mysql, docker-build-and-push, deploy-container-stub] \ No newline at end of file diff --git a/src/DbContextLib/DbContextLib.csproj b/src/DbContextLib/DbContextLib.csproj index a2d1b08..cd47dbc 100644 --- a/src/DbContextLib/DbContextLib.csproj +++ b/src/DbContextLib/DbContextLib.csproj @@ -10,6 +10,7 @@ + diff --git a/src/DbContextLib/HeartTrackContext.cs b/src/DbContextLib/HeartTrackContext.cs index 3264346..1ce7564 100644 --- a/src/DbContextLib/HeartTrackContext.cs +++ b/src/DbContextLib/HeartTrackContext.cs @@ -64,6 +64,22 @@ namespace DbContextLib /// The options for the context. public HeartTrackContext(DbContextOptions options) : base(options) { } + + public HeartTrackContext(string dbPlatformPath) + : this(InitPlaformDb(dbPlatformPath)) + { + } + + private static DbContextOptions InitPlaformDb(string dbPlatformPath) + { + var options = new DbContextOptionsBuilder() + .UseMySql($"{dbPlatformPath}", new MySqlServerVersion(new Version(10, 11, 1))) + .Options; + return options; + } + + + /// /// Configures the database options if they are not already configured. diff --git a/src/HeartTrack.sln b/src/HeartTrack.sln index b4faecf..6b62741 100644 --- a/src/HeartTrack.sln +++ b/src/HeartTrack.sln @@ -25,8 +25,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestsAPI", "TestsAPI", "{30 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientTests", "Tests\TestsAPI\ClientTests\ClientTests.csproj", "{9E4D3AC5-E6CA-4753-BD96-BF5EE793931A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestsXUnit", "Tests\TestsAPI\TestsXUnit\TestsXUnit.csproj", "{44C367DC-5FE0-4CF2-9E76-A0282E931853}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Model", "Model\Model.csproj", "{30AB7FAA-6072-40B6-A15E-9188B59144F9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestApi", "Tests\TestsAPI\UnitTestApi\UnitTestApi.csproj", "{E515C8B6-6282-4D8B-8523-7B3A13E4AF58}" @@ -88,10 +86,6 @@ Global {9E4D3AC5-E6CA-4753-BD96-BF5EE793931A}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E4D3AC5-E6CA-4753-BD96-BF5EE793931A}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E4D3AC5-E6CA-4753-BD96-BF5EE793931A}.Release|Any CPU.Build.0 = Release|Any CPU - {44C367DC-5FE0-4CF2-9E76-A0282E931853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44C367DC-5FE0-4CF2-9E76-A0282E931853}.Debug|Any CPU.Build.0 = Debug|Any CPU - {44C367DC-5FE0-4CF2-9E76-A0282E931853}.Release|Any CPU.ActiveCfg = Release|Any CPU - {44C367DC-5FE0-4CF2-9E76-A0282E931853}.Release|Any CPU.Build.0 = Release|Any CPU {30AB7FAA-6072-40B6-A15E-9188B59144F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {30AB7FAA-6072-40B6-A15E-9188B59144F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {30AB7FAA-6072-40B6-A15E-9188B59144F9}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -146,7 +140,6 @@ Global {2D166FAD-4934-474B-96A8-6C0635156EC2} = {2B227C67-3BEC-4A83-BDA0-F3918FBC0D18} {30FC2BE9-7397-445A-84AD-043CE70F4281} = {2B227C67-3BEC-4A83-BDA0-F3918FBC0D18} {9E4D3AC5-E6CA-4753-BD96-BF5EE793931A} = {30FC2BE9-7397-445A-84AD-043CE70F4281} - {44C367DC-5FE0-4CF2-9E76-A0282E931853} = {30FC2BE9-7397-445A-84AD-043CE70F4281} {E515C8B6-6282-4D8B-8523-7B3A13E4AF58} = {30FC2BE9-7397-445A-84AD-043CE70F4281} {31FA8E5E-D642-4C43-A2B2-02B9832B2CEC} = {2B227C67-3BEC-4A83-BDA0-F3918FBC0D18} {73EA27F2-9F0C-443F-A5EE-2960C983A422} = {2B227C67-3BEC-4A83-BDA0-F3918FBC0D18} diff --git a/src/HeartTrackAPI/AppBootstrap.cs b/src/HeartTrackAPI/AppBootstrap.cs index 4c5200a..f8e97b0 100644 --- a/src/HeartTrackAPI/AppBootstrap.cs +++ b/src/HeartTrackAPI/AppBootstrap.cs @@ -1,6 +1,8 @@ +using System.Reflection; using DbContextLib; using DbContextLib.Identity; using Entities; +using HeartTrackAPI.Utils; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc.ApiExplorer; @@ -12,6 +14,7 @@ using Microsoft.OpenApi.Models; using Model.Manager; using Model2Entities; using StubAPI; +using Swashbuckle.AspNetCore.SwaggerGen; namespace HeartTrackAPI; @@ -23,16 +26,14 @@ public class AppBootstrap(IConfiguration configuration) { services.AddControllers(); services.AddEndpointsApiExplorer(); - //services.AddTransient, ConfigureSwaggerOption>(); + AddSwagger(services); // include Xml comment // addsecurityRequiment // securityDef - services.AddSwaggerGen(); AddHeartTrackContextServices(services); AddModelService(services); AddIdentityServices(services); AddApiVersioning(services); - AddSwagger(services); services.AddHealthChecks(); @@ -40,20 +41,37 @@ public class AppBootstrap(IConfiguration configuration) private void AddHeartTrackContextServices(IServiceCollection services) { - var connectionString = Configuration.GetConnectionString("HeartTrackAuthConnection"); - if (string.IsNullOrWhiteSpace(connectionString)) + string connectionString; + + switch (System.Environment.GetEnvironmentVariable("TYPE")) { - throw new InvalidOperationException("The connection string for the database is not set."); + case "BDD": + var HOST = System.Environment.GetEnvironmentVariable("HOST"); + var PORT = System.Environment.GetEnvironmentVariable("PORT"); + var DATABASE = System.Environment.GetEnvironmentVariable("DATABASE"); + var USERNAME = System.Environment.GetEnvironmentVariable("USERNAME"); + var PASSWORD = System.Environment.GetEnvironmentVariable("PASSWORD"); + + connectionString = $"server={HOST};port={PORT};database={DATABASE};user={USERNAME};password={PASSWORD}"; + services.AddSingleton(provider => new DbDataManager(connectionString)); + + break; + default: + connectionString = Configuration.GetConnectionString("HeartTrackAuthConnection"); + if (string.IsNullOrWhiteSpace(connectionString)) + { + services.AddDbContext(options => options.UseInMemoryDatabase("AuthDb")); + //options => options.UseSqlite(connectionString) + //services.AddDbContext(); + services.AddDbContext(options => + options.UseSqlite(connectionString), ServiceLifetime.Singleton); + } + break; + } - else - { - services.AddDbContext(options => options.UseInMemoryDatabase("AuthDb")); - //options => options.UseSqlite(connectionString) - //services.AddDbContext(); - services.AddDbContext(options => - options.UseSqlite(connectionString), ServiceLifetime.Singleton); + /* - services.AddSingleton>(provider => + services.AddSingleton>(provider => { var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); @@ -64,9 +82,8 @@ public class AppBootstrap(IConfiguration configuration) return options; });*/ - } - } + private void AddModelService(IServiceCollection services) { //services.AddSingleton(provider => new DbDataManager(provider.GetService())); @@ -104,16 +121,57 @@ public class AppBootstrap(IConfiguration configuration) private void AddSwagger(IServiceCollection services) { services.AddSwaggerGen(options => + { + options.OperationFilter(); + + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + options.IncludeXmlComments(xmlPath); + + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = + "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey + }); + var scheme = new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + }, + Scheme = "oauth2", + Name = "Bearer", + In = ParameterLocation.Header, + }, + new List() + } + }; + options.AddSecurityRequirement(scheme); + }); + services.AddTransient, SwaggerOptions>(); + services.AddSwaggerGen(options => + { + options.OperationFilter(); + }); + /* services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "HeartTrackAPI", Version = "v1" }); options.SwaggerDoc("v2", new OpenApiInfo { Title = "HeartTrackAPI", Version = "v2" }); - }); + });*/ services.AddVersionedApiExplorer(setup => { setup.GroupNameFormat = "'v'VVV"; setup.SubstituteApiVersionInUrl = true; }); + } public void Configure(WebApplication app, IWebHostEnvironment env) diff --git a/src/HeartTrackAPI/Controllers/ActivityController.cs b/src/HeartTrackAPI/Controllers/ActivityController.cs index efb07a9..d20e167 100644 --- a/src/HeartTrackAPI/Controllers/ActivityController.cs +++ b/src/HeartTrackAPI/Controllers/ActivityController.cs @@ -10,7 +10,8 @@ using Model.Repository; namespace HeartTrackAPI.Controllers; [ApiController] -[Route("api/activities")] +[ApiVersion("1.0")] +[Route("api/v{version:apiVersion}/[controller]")] public class ActivityController : Controller { private readonly IActivityRepository _activityService; diff --git a/src/HeartTrackAPI/Controllers/UsersController.cs b/src/HeartTrackAPI/Controllers/UsersController.cs index e0d571d..69d2bc9 100644 --- a/src/HeartTrackAPI/Controllers/UsersController.cs +++ b/src/HeartTrackAPI/Controllers/UsersController.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using APIMappers; using Dto; using HeartTrackAPI.Request; @@ -9,10 +10,13 @@ using Model.Repository; using Shared; namespace HeartTrackAPI.Controllers; - +/// +/// Contrôle les actions liées aux utilisateurs dans l'application HeartTrack. +/// Gère les opérations CRUD sur les utilisateurs, leurs amis, et leurs activités. +/// [ApiController] [ApiVersion("1.0")] -[Route("api/v{version:apiVersion}/users")] +[Route("api/v{version:apiVersion}/[controller]")] public class UsersController : Controller { private readonly ILogger _logger; @@ -24,7 +28,15 @@ public class UsersController : Controller _userService = dataManager.UserRepo; _activityService = dataManager.ActivityRepo; } - + + /// + /// Récupère une page d'utilisateurs en fonction des critères de pagination et de tri fournis. + /// + /// Les critères de pagination et de tri pour les utilisateurs. + /// Une page de données utilisateur selon les critères spécifiés. + /// Retourne la page demandée d'utilisateurs. + /// La demande de pagination est invalide. + /// Erreur interne du serveur. [HttpGet] [ProducesResponseType(typeof(PageResponse), 200)] [ProducesResponseType(400)] @@ -49,15 +61,23 @@ public class UsersController : Controller catch (Exception e) { _logger.LogError(e, "Error while getting all athletes"); - return StatusCode(500); + return Problem(); } } + /// + /// Récupère un utilisateur spécifique par son identifiant. + /// + /// L'identifiant de l'utilisateur à récupérer. + /// L'utilisateur correspondant à l'identifiant spécifié. + /// Retourne l'utilisateur demandé. + /// Aucun utilisateur trouvé pour l'identifiant spécifié. + /// Erreur interne du serveur. [HttpGet("{id}")] [ProducesResponseType(typeof(UserDto), 200)] [ProducesResponseType(404)] [ProducesResponseType(500)] - public async Task> GetById(int id) + public async Task> GetById([Range(0,int.MaxValue)]int id) { try { @@ -73,11 +93,16 @@ public class UsersController : Controller catch (Exception e) { _logger.LogError(e, "Error while getting athlete by id {id}", id); - return StatusCode(500); + return Problem(); } } - + /// + /// Obtient le nombre total d'utilisateurs. + /// + /// Le nombre total d'utilisateurs. + /// Retourne le nombre total d'utilisateurs. + /// Erreur interne du serveur. [HttpGet("count")] [ProducesResponseType(typeof(int), 200)] [ProducesResponseType(500)] @@ -92,10 +117,19 @@ public class UsersController : Controller catch (Exception e) { _logger.LogError(e, "Error while getting the number of users"); - return StatusCode(500); + return Problem(); } } + /// + /// Met à jour les informations d'un utilisateur spécifique. + /// + /// L'identifiant de l'utilisateur à mettre à jour. + /// Les données de l'utilisateur pour la mise à jour. + /// L'utilisateur mis à jour. + /// Retourne l'utilisateur mis à jour. + /// Utilisateur non trouvé. + /// Erreur interne du serveur. [HttpPut("{id}")] [ProducesResponseType(typeof(UserDto), 200)] [ProducesResponseType(404)] @@ -115,7 +149,7 @@ public class UsersController : Controller if(updatedAthlete == null) { _logger.LogError("Error while updating athlete with id {id}", id); - return StatusCode(500); + return Problem(); } return Ok(updatedAthlete.ToDto()); @@ -123,10 +157,18 @@ public class UsersController : Controller catch (Exception e) { _logger.LogError(e, "Error while getting the number of users"); - return StatusCode(500); + return Problem(); } } + /// + /// Supprime un utilisateur spécifique. + /// + /// L'identifiant de l'utilisateur à supprimer. + /// Action result. + /// Utilisateur supprimé avec succès. + /// Utilisateur non trouvé. + /// Erreur interne du serveur. [HttpDelete("{id}")] [ProducesResponseType(200)] [ProducesResponseType(404)] @@ -148,17 +190,26 @@ public class UsersController : Controller if(!isDeleted) { _logger.LogError("Error while deleting athlete with id {id}", id); - return StatusCode(500); + return Problem(); } return Ok(); } catch (Exception e) { _logger.LogError(e, "Error while getting the number of users"); - return StatusCode(500); + return Problem(); } } + /// + /// Obtient la liste des amis d'un utilisateur spécifique. + /// + /// L'identifiant de l'utilisateur. + /// Les critères de pagination et de tri. + /// La liste paginée des amis. + /// Retourne la liste paginée des amis de l'utilisateur. + /// Utilisateur non trouvé. + /// Erreur interne du serveur. [HttpGet("{id}/friends")] [ProducesResponseType(typeof(PageResponse), 200)] [ProducesResponseType(404)] @@ -188,11 +239,19 @@ public class UsersController : Controller catch (Exception e) { _logger.LogError(e, "Error while getting the number of users"); - return StatusCode(500); + return Problem(); } } - + /// + /// Ajoute un ami à un utilisateur spécifique. + /// + /// L'identifiant de l'utilisateur. + /// L'identifiant de l'ami à ajouter. + /// Action result. + /// Ami ajouté avec succès. + /// Utilisateur ou ami non trouvé. + /// Erreur interne du serveur. [HttpPost("{id}/friend/{friendId}")] [ProducesResponseType(200)] [ProducesResponseType(404)] @@ -218,17 +277,27 @@ public class UsersController : Controller if(!isAdded) { _logger.LogError("Error while adding friend with id {friendId} to athlete with id {id}", friendId, id); - return StatusCode(500); + return Problem(); } return Ok(); } catch (Exception e) { _logger.LogError(e, "Error while getting the number of users"); - return StatusCode(500); + return Problem(); } } + + /// + /// Supprime un ami d'un utilisateur spécifique. + /// + /// L'identifiant de l'utilisateur. + /// L'identifiant de l'ami à supprimer. + /// Action result. + /// Ami supprimé avec succès. + /// Utilisateur ou ami non trouvé. + /// Erreur interne du serveur. [HttpDelete("{id}/friend/{friendId}")] [ProducesResponseType(200)] [ProducesResponseType(404)] @@ -254,19 +323,28 @@ public class UsersController : Controller if(!isRemoved) { _logger.LogError("Error while removing friend with id {friendId} to athlete with id {id}", friendId, id); - return StatusCode(500); + return Problem(); } return Ok(); } catch (Exception e) { _logger.LogError(e, "Error while getting the number of users"); - return StatusCode(500); + return Problem(); } } - // ou faire un get qui si le role est coach resend les athletes et si le role est athlete resend les coach + // #[TODO] [Dave] ou faire un get qui si le role est coach resend les athletes et si le role est athlete resend les coach + /// + /// Obtient la liste des athlètes d'un coach spécifique. + /// + /// L'identifiant du coach. + /// Les critères de pagination et de tri. + /// La liste paginée des athlètes. + /// Retourne la liste paginée des athlètes du coach. + /// Coach non trouvé. + /// Erreur interne du serveur. [HttpGet("{coachId}/athletes")] [ProducesResponseType(typeof(PageResponse), 200)] [ProducesResponseType(404)] @@ -296,10 +374,19 @@ public class UsersController : Controller catch (Exception e) { _logger.LogError(e, "Error while getting the number of users"); - return StatusCode(500); + return Problem(); } } + /// + /// Obtient la liste des activités d'un utilisateur spécifique. + /// + /// L'identifiant de l'utilisateur. + /// Les critères de pagination et de tri. + /// La liste paginée des activités de l'utilisateur. + /// Retourne la liste paginée des activités. + /// Aucune activité trouvée. + /// Erreur interne du serveur. [HttpGet("{userId}/activities")] // should be tiny DTOActivity returned with only the necessary information (will be used in the list of activities of a user) public async Task>> GetActivitiesByUser(int userId, [FromQuery] PageRequest pageRequest) @@ -324,20 +411,26 @@ public class UsersController : Controller catch (Exception e) { _logger.LogError(e, "Error while getting all activities"); - return StatusCode(500); + return Problem(); } } - /* - + /* [TODO] [Dave] [HttpGet("{userId}/trainings")] [ProducesResponseType(typeof(PageResponse), 200)] [ProducesResponseType(404)] [ProducesResponseType(500)] -public async Task> GetTrainings(int userId, [FromQuery] PageRequest request) + public async Task> GetTrainings(int userId, [FromQuery] PageRequest request) */ - - + /// + /// Déconnecte l'utilisateur actuel. + /// + /// Le gestionnaire de connexion. + /// Paramètre vide utilisé pour s'assurer que la requête provient bien d'un client. + /// Action result. + /// Déconnexion réussie. + /// Déconnexion non autorisée. + /// Erreur interne du serveur. [HttpPost("logout")] [ProducesResponseType(200)] [ProducesResponseType(401)] diff --git a/src/HeartTrackAPI/HeartTrackAPI.csproj b/src/HeartTrackAPI/HeartTrackAPI.csproj index 2329d47..d8fac4c 100644 --- a/src/HeartTrackAPI/HeartTrackAPI.csproj +++ b/src/HeartTrackAPI/HeartTrackAPI.csproj @@ -5,6 +5,8 @@ enable enable true + true + $(NoWarn);1591 diff --git a/src/HeartTrackAPI/Request/PageRequest.cs b/src/HeartTrackAPI/Request/PageRequest.cs index 9fad40d..0c93406 100644 --- a/src/HeartTrackAPI/Request/PageRequest.cs +++ b/src/HeartTrackAPI/Request/PageRequest.cs @@ -1,10 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding; + namespace HeartTrackAPI.Request; public class PageRequest { - - public string? OrderingPropertyName { get; set; } = null;// need to be map on the dto OrderCriteria + public string? OrderingPropertyName { get; set; } = null; public bool? Descending { get; set; } = false; + + [Range(1, int.MaxValue, ErrorMessage = "Count must be greater than 0")] public int Index { get; set; } = 0; - public int Count { get; set; } = 5; + + [Range(1, int.MaxValue, ErrorMessage = "Count must be greater than 0")] + public int Count { get; set; } = 1; } diff --git a/src/HeartTrackAPI/Utils/SwaggerDefaultValues.cs b/src/HeartTrackAPI/Utils/SwaggerDefaultValues.cs new file mode 100644 index 0000000..dd8671c --- /dev/null +++ b/src/HeartTrackAPI/Utils/SwaggerDefaultValues.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace HeartTrackAPI.Utils; + +public class SwaggerDefaultValues : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var apiDescription = context.ApiDescription; + operation.Deprecated |= apiDescription.IsDeprecated(); + + foreach (var responseType in context.ApiDescription.SupportedResponseTypes) + { + var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); + var response = operation.Responses[responseKey]; + + foreach (var contentType in response.Content.Keys) + { + if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType)) + { + response.Content.Remove(contentType); + } + } + } + + if (operation.Parameters == null) + { + return; + } + + foreach (var parameter in operation.Parameters) + { + var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); + + parameter.Description ??= description.ModelMetadata?.Description; + + if (parameter.Schema.Default == null && + description.DefaultValue != null && + description.DefaultValue is not DBNull && + description.ModelMetadata is ModelMetadata modelMetadata) + { + var json = JsonSerializer.Serialize(description.DefaultValue, modelMetadata.ModelType); + parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json); + } + + parameter.Required |= description.IsRequired; + } + } +} \ No newline at end of file diff --git a/src/HeartTrackAPI/Utils/SwaggerOptions.cs b/src/HeartTrackAPI/Utils/SwaggerOptions.cs index e9aed2c..02c92d8 100644 --- a/src/HeartTrackAPI/Utils/SwaggerOptions.cs +++ b/src/HeartTrackAPI/Utils/SwaggerOptions.cs @@ -50,8 +50,12 @@ public class SwaggerOptions: IConfigureNamedOptions { var info = new OpenApiInfo() { - Title = ".NET Core (.NET 6) Web API For Lol", - Version = desc.ApiVersion.ToString() + Title = "Web API For HeartTrack .NET 8", + Version = desc.ApiVersion.ToString(), + Description = "The HeartTrack project API, aims to provide an Open Source solution for heart rate data analysis.", + Contact = new OpenApiContact { Name = "HeartTrackDev", Email = "toto@toto.fr" }, + License = new OpenApiLicense { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") } + }; if (desc.IsDeprecated) diff --git a/src/Model2Entities/DbDataManager.cs b/src/Model2Entities/DbDataManager.cs index d336fe9..6603fb9 100644 --- a/src/Model2Entities/DbDataManager.cs +++ b/src/Model2Entities/DbDataManager.cs @@ -22,6 +22,13 @@ public partial class DbDataManager: IDataManager ActivityMapper.Reset(); // Faire pour les autres reset() des autres mappers } + + public DbDataManager(string dbPlatformPath) + : this(new HeartTrackContext(dbPlatformPath)) + { + DbContext.Database.EnsureCreated(); + } + public DbDataManager() { diff --git a/src/Tests/TestsAPI/TestsXUnit/GlobalUsings.cs b/src/Tests/TestsAPI/TestsXUnit/GlobalUsings.cs deleted file mode 100644 index 8c927eb..0000000 --- a/src/Tests/TestsAPI/TestsXUnit/GlobalUsings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Xunit; \ No newline at end of file diff --git a/src/Tests/TestsAPI/TestsXUnit/TestsXUnit.csproj b/src/Tests/TestsAPI/TestsXUnit/TestsXUnit.csproj deleted file mode 100644 index 22b0134..0000000 --- a/src/Tests/TestsAPI/TestsXUnit/TestsXUnit.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - diff --git a/src/Tests/TestsAPI/TestsXUnit/UnitTest1.cs b/src/Tests/TestsAPI/TestsXUnit/UnitTest1.cs deleted file mode 100644 index 70d745a..0000000 --- a/src/Tests/TestsAPI/TestsXUnit/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace TestsXUnit; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - - } -} \ No newline at end of file diff --git a/src/Tests/TestsAPI/UnitTestApi/Controllers/UsersControllerTest.cs b/src/Tests/TestsAPI/UnitTestApi/Controllers/UsersControllerTest.cs new file mode 100644 index 0000000..1d1500c --- /dev/null +++ b/src/Tests/TestsAPI/UnitTestApi/Controllers/UsersControllerTest.cs @@ -0,0 +1,225 @@ +using ApiMappeur; +using Dto; +using HeartTrackAPI.Controllers; +using HeartTrackAPI.Request; +using HeartTrackAPI.Responce; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging.Abstractions; +using Model; +using Model.Manager; +using Model.Repository; +using Moq; +using Shared; +using StubAPI; + +namespace UnitTestApi.Controllers; + +[TestClass] +[TestSubject(typeof(UsersController))] +public class UsersControllerTest +{ + private Mock _dataManagerMock; + private IDataManager _dataManager; + private UsersController _usersController; + + private readonly List _users = + [ + new User + { + Id = 1, Username = "DoeDoe", + ProfilePicture = + "https://images.unsplash.com/photo-1682687982134-2ac563b2228b?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + FirstName = "John", LastName = "Doe", + Sexe = "M", Lenght = 180, Weight = 70, DateOfBirth = new DateTime(1990, 1, 1), + Email = "john.doe@example.com", Role = new Athlete() + }, + + new User + { + Id = 2, Username = "SmithSmith", + ProfilePicture = + "https://images.unsplash.com/photo-1709507779917-242b560288be?q=80&w=2080&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + FirstName = "Jane", LastName = "Smith", + Sexe = "F", Lenght = 170, Weight = 60, DateOfBirth = new DateTime(1992, 2, 2), + Email = "athlete2@example.com", Role = new Coach() + }, + + new User + { + Id = 3, Username = "Athlete3", + ProfilePicture = + "https://plus.unsplash.com/premium_photo-1705091981693-6006f8a20479?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + FirstName = "First3", LastName = "Last3", + Sexe = "M", Lenght = 190, Weight = 80, DateOfBirth = new DateTime(1994, 3, 3), Email = "ath@ex.fr", + Role = new Athlete() + } + ]; + + [TestInitialize] + public void SetUp() + { + _dataManagerMock = new Mock(); + + _dataManagerMock.Setup(dm => dm.UserRepo.GetNbItems()).ReturnsAsync(_users.Count); + _dataManagerMock.Setup(dm => + dm.UserRepo.GetUsers(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())).ReturnsAsync( + (int index, int count, AthleteOrderCriteria criteria, bool descending) => + _users.GetItemsWithFilterAndOrdering(c => true, index, count, + criteria != AthleteOrderCriteria.None ? criteria : null, descending) + ); + + + + _usersController = new UsersController(new NullLogger(), _dataManagerMock.Object); + } +/* + [TestInitialize] + public void SetUp() + { + _dataManager = new StubData(); + _usersController = new UsersController(new NullLogger(), _dataManager); + }*/ + + + [TestMethod] + public async Task Get_ReturnsPageResponse_WhenRequestIsValid() + { + // Arrange + var request = new PageRequest + { + Index = 0, + Count = 3, + OrderingPropertyName = "Id", + Descending = false + }; + + // Act + var result = await _usersController.Get(request); + Assert.IsInstanceOfType(result.Result, typeof(OkObjectResult)); + var okResult = result.Result as OkObjectResult; + // Assert + Assert.IsNotNull(okResult); + Assert.IsInstanceOfType(okResult.Value, typeof(PageResponse)); + var pageResponse = okResult.Value as PageResponse; + Assert.IsNotNull(pageResponse); + Assert.AreEqual(3, pageResponse.Items.Count()); + Assert.AreEqual(3, pageResponse.Total); + Assert.AreEqual(0, pageResponse.Index); + Assert.AreEqual(3, pageResponse.Count); + Assert.AreEqual(3, pageResponse.Count); + } + + [DataTestMethod] + [DataRow(0, 2, "Id", false, 2)] + [DataRow(1, 1, "Id", false, 1)] + [DataRow(0, 3, "Id", true, 3)] + public async Task Get_ReturnsCorrectPaginationAndOrdering(int index, int count, string orderingProperty, + bool descending, int expectedItemCount) + { + // Arrange + var request = new PageRequest + { + Index = index, + Count = count, + OrderingPropertyName = orderingProperty, + Descending = descending + }; + // Act + var result = await _usersController.Get(request); + Assert.IsInstanceOfType(result.Result, typeof(OkObjectResult)); + var okResult = result.Result as OkObjectResult; + // Assert + Assert.IsNotNull(okResult); + Assert.IsInstanceOfType(okResult.Value, typeof(PageResponse)); + var pageResponse = okResult.Value as PageResponse; + Assert.IsNotNull(pageResponse); + Assert.AreEqual(expectedItemCount, pageResponse.Items.Count()); + } + + [TestMethod] + public async Task Get_ReturnsInternalServerError_OnException() + { + _dataManagerMock.Setup(dm => + dm.UserRepo.GetUsers(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny())) + .ThrowsAsync(new Exception("Simulated database failure.")); + + var request = new PageRequest { Index = 0, Count = 3 }; + + var result = await _usersController.Get(request); + + Assert.IsInstanceOfType(result.Result, typeof(ObjectResult)); + var objectResult = result.Result as ObjectResult; + Assert.AreEqual(500, objectResult.StatusCode); + } + + + + [TestMethod] + public async Task GetById_ReturnsUserDto_WhenRequestIsValid() + { + // Arrange + var id = 1; + _dataManagerMock.Setup(dm => dm.UserRepo.GetItemById(id)).ReturnsAsync(_users.First(x => x.Id == id)); + + // Act + var result = await _usersController.GetById(id) ; + Assert.IsInstanceOfType(result.Result, typeof(OkObjectResult)); + var okResult = result.Result as OkObjectResult; + + // Assert + Assert.IsNotNull(okResult); + var resultObject = result.Result as OkObjectResult; + Assert.IsNotNull(resultObject); + Assert.IsInstanceOfType(resultObject.Value, typeof(UserDto)); + var user = resultObject.Value as UserDto; + Assert.IsNotNull(user); + var tmp = _users.First(x => x.Id == id).ToDto(); + Assert.AreEqual(tmp.Id, user.Id); + } + + [TestMethod] + public async Task GetById_ReturnsUserDto_WhenRequestUserDoesNotExist() + { + // Arrange + var id = 0; + _dataManagerMock.Setup(dm => dm.UserRepo.GetItemById(id)).ReturnsAsync((User)null!); + + // Act + var result = await _usersController.GetById(id) ; + + // Assert + Assert.IsInstanceOfType(result.Result, typeof(NotFoundObjectResult)); + } + + + [TestMethod] + public async Task GetById_Returns404_WhenIdIsInvalid() + { + // Arrange + var id = -2; + + // Act + var result = await _usersController.GetById(id); + + // Assert + Assert.IsInstanceOfType(result.Result, typeof(NotFoundObjectResult)); + } + + + [TestMethod] + public async Task Count_ReturnsInt_WhenRequestIsValid() + { + // Act + var result = await _usersController.Count(); + Assert.IsNotNull(result); + result = result.Result as OkObjectResult; + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result.Value, typeof(int)); + } + +} \ No newline at end of file diff --git a/src/Tests/TestsAPI/UnitTestApi/UnitTestApi.csproj b/src/Tests/TestsAPI/UnitTestApi/UnitTestApi.csproj index 45b5c8f..9931d8b 100644 --- a/src/Tests/TestsAPI/UnitTestApi/UnitTestApi.csproj +++ b/src/Tests/TestsAPI/UnitTestApi/UnitTestApi.csproj @@ -10,7 +10,9 @@ + + diff --git a/src/Tests/TestsAPI/UnitTestApi/UserControllerTest.cs b/src/Tests/TestsAPI/UnitTestApi/UserControllerTest.cs deleted file mode 100644 index dd511e7..0000000 --- a/src/Tests/TestsAPI/UnitTestApi/UserControllerTest.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Dto; -using HeartTrackAPI.Controllers; -using HeartTrackAPI.Request; -using HeartTrackAPI.Responce; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging.Abstractions; -using Model.Manager; -using Model.Repository; -using StubAPI; - -namespace UnitTestApi; - -[TestClass] -public class UserControllerTest -{ - private readonly IDataManager StubDataManager; - private readonly UsersController _usersController; - - public UserControllerTest() - { - StubDataManager = new StubData(); - _usersController = new UsersController(new NullLogger(), StubDataManager); - - } - - [TestMethod] - public void Get_ReturnsPageResponse_WhenRequestIsValid() - { - // Arrange - var request = new PageRequest - { - Index = 0, - Count = 10, - OrderingPropertyName = "Id", - Descending = false - }; - - // Act - //var result = _usersController.Get(request).Result as OkObjectResult; - - // Assert - // Assert.IsNotNull(result); - //Assert.IsInstanceOfType(result.Value, typeof(PageResponse)); - } - /* - [TestMethod] - public void GetById_ReturnsUserDto_WhenRequestIsValid() - { - // Arrange - var id = 1; - - // Act - var result = _usersController.GetById(id).Result as OkObjectResult; - - // Assert - Assert.IsNotNull(result); - Assert.IsInstanceOfType(result.Value, typeof(UserDto)); - } - - [TestMethod] - public void GetById_Returns404_WhenIdIsInvalid() - { - // Arrange - var id = 0; - - // Act - var result = _usersController.GetById(id).Result as NotFoundResult; - - // Assert - Assert.IsNotNull(result); - } - - [TestMethod] - public void GetById_Returns500_WheExceptionIsThrown() - { - // Arrange - var id = 0; - - // Act - var result = _usersController.GetById(id).Result as StatusCodeResult; - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(500, result.StatusCode); - } - - [TestMethod] - public void Count_ReturnsInt_WhenRequestIsValid() - { - // Act - var result = _usersController.Count().Result as OkObjectResult; - - // Assert - Assert.IsNotNull(result); - Assert.IsInstanceOfType(result.Value, typeof(int)); - } - - [TestMethod] - public void Count_Returns500_WheExceptionIsThrown() - { - // Act - var result = _usersController.Count().Result as StatusCodeResult; - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(500, result.StatusCode); - } - - [TestMethod] - public void Update_ReturnsUserDto_WhenRequestIsValid() - { - // Arrange - var id = 1; - var user = new UserDto - { - Id = 1, - FirstName = "John", - LastName = "Doe", - Email = "toto@eoeo.fr", - }; - - }*/ - -} \ No newline at end of file