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