Merge branch 'merging_EF-API' of codefirst.iut.uca.fr:HeartDev/API into merging_EF-API

merging_EF-API
Antoine PEREDERII 1 year ago
commit a5e602c533

@ -3,9 +3,6 @@ type: docker
name: HeartTrack-API
trigger:
branch:
- WORK-CD
- WORK-EF_WebAPI
event:
- push
@ -18,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 ]
@ -36,8 +41,55 @@ 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
environment:
CODEFIRST_CLIENTDRONE_ENV_DOTNET_ROLL_FORWARD: LatestMajor
CODEFIRST_CLIENTDRONE_ENV_DOTNET_ROLL_FORWARD_TO_PRERELEASE: 1
commands:
- cd src/
- dotnet restore HeartTrack.sln
- cd HeartTrackAPI
- 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 HeartTrackAPI/bin/Release/net8.0/HeartTrackAPI.dll v1
depends_on: [build,tests]
- 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: docker-build-and-push
image: plugins/docker
settings:
@ -49,25 +101,67 @@ steps:
from_secret: SECRET_REGISTRY_USERNAME
password:
from_secret: SECRET_REGISTRY_PASSWORD
depends_on: [ build ]
# 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: heart_stub
COMMAND: create
OVERWRITE: true
depends_on: [ docker-build-and-push ]
# - 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 ]
- name: deploy-container
# 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_PORTDB: 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
CONTAINERNAME: 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 ]
- 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 ]
depends_on: [deploy-container-mysql, docker-build-and-push, deploy-container-stub]

3
.gitignore vendored

@ -565,4 +565,7 @@ xcuserdata/
/dataSources.local.xml
.ideaMigration/
Migration/
Migrations/
*.db

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,53 @@
using Dto;
using Model;
using Shared;
namespace APIMappers;
public static class ActivityMapper
{
private static GenericMapper<Activity, ActivityDto> _mapper = new GenericMapper<Activity, ActivityDto>();
public static ActivityDto ToDto(this Activity activity)
{
return activity.ToU(_mapper, activityDto => new ActivityDto
{
Id = activity.Id,
Type = activity.Type,
Date = activity.Date,
StartTime = activity.StartTime,
EndTime = activity.EndTime,
EffortFelt = activity.Effort,
Variability = activity.Variability,
Variance = activity.Variance,
StandardDeviation = activity.StandardDeviation,
Average = activity.Average,
Maximum = activity.Maximum,
Minimum = activity.Minimum,
AverageTemperature = activity.AverageTemperature,
HasAutoPause = activity.HasAutoPause
});
}
public static Activity ToModel(this ActivityDto activityDto)
{
return activityDto.ToT(_mapper, activity => new Activity
{
Id = activityDto.Id,
Type = activityDto.Type,
Date = activityDto.Date,
StartTime = activityDto.StartTime,
EndTime = activityDto.EndTime,
Effort = activityDto.EffortFelt,
Variability = activityDto.Variability,
Variance = activityDto.Variance,
StandardDeviation = activityDto.StandardDeviation,
Average = activityDto.Average,
Maximum = activityDto.Maximum,
Minimum = activityDto.Minimum,
AverageTemperature = activityDto.AverageTemperature,
HasAutoPause = activityDto.HasAutoPause
});
}
}

@ -0,0 +1,54 @@
using Dto;
using Model;
using Shared;
namespace APIMappers;
public static class UserMappeur
{
private static GenericMapper<User, UserDto> _mapper = new GenericMapper<User, UserDto>();
public static UserDto ToDto(this User user)
{
return user.ToU(_mapper, userDto => new UserDto
{
Id = user.Id,
Username = user.Username,
ProfilePicture = user.ProfilePicture,
LastName = user.LastName,
FirstName = user.FirstName,
Email = user.Email,
Password = user.MotDePasse,
Sexe = user.Sexe,
Lenght = user.Lenght,
Weight = user.Weight,
DateOfBirth = user.DateOfBirth,
IsCoach = user.Role is Coach
});
// ), (activity, entity) => activity.Id = entity.IdActivity);
}
// ochestrateur => api gateway
// corégraphie => microservice TCP
public static User ToModel(this UserDto userDto)
{
return userDto.ToT(_mapper, user => new User
{
Username = userDto.Username,
ProfilePicture = userDto.ProfilePicture,
LastName = userDto.LastName,
FirstName = userDto.FirstName,
Email = userDto.Email,
MotDePasse = userDto.Password,
Sexe = userDto.Sexe,
Lenght = userDto.Lenght,
Weight = userDto.Weight,
DateOfBirth = userDto.DateOfBirth,
Role = userDto.IsCoach ? new Coach() : new Athlete()
});
}
}

@ -10,6 +10,7 @@
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.1" />
</ItemGroup>
<ItemGroup>

@ -55,13 +55,31 @@ namespace DbContextLib
/// <summary>
/// Initializes a new instance of the <see cref="HeartTrackContext"/> class.
/// </summary>
public HeartTrackContext() : base() { }
public HeartTrackContext() : base()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="HeartTrackContext"/> class with the specified options.
/// </summary>
/// <param name="options">The options for the context.</param>
public HeartTrackContext(DbContextOptions<HeartTrackContext> options) : base(options) { }
public HeartTrackContext(DbContextOptions<HeartTrackContext> options) : base(options)
{ }
public HeartTrackContext(string dbPlatformPath)
: this(InitPlaformDb(dbPlatformPath))
{
}
private static DbContextOptions<HeartTrackContext> InitPlaformDb(string dbPlatformPath)
{
var options = new DbContextOptionsBuilder<HeartTrackContext>()
.UseMySql($"{dbPlatformPath}", new MySqlServerVersion(new Version(10, 11, 1)))
.Options;
return options;
}
/// <summary>
/// Configures the database options if they are not already configured.

@ -3,17 +3,17 @@ namespace Dto;
public class ActivityDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public DateTime Date { get; set; }
public TimeSpan Duration { get; set; }
public float Distance { get; set; }
public float Elevation { get; set; }
public float AverageSpeed { get; set; }
public int AverageHeartRate { get; set; }
public int Calories { get; set; }
public string Description { get; set; }
public string? Gpx { get; set; }
public string? Image { get; set; }
public int AthleteId { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public int EffortFelt { get; set; }
public float Variability { get; set; }
public float Variance { get; set; }
public float StandardDeviation { get; set; }
public float Average { get; set; }
public int Maximum { get; set; }
public int Minimum { get; set; }
public float AverageTemperature { get; set; }
public bool HasAutoPause { get; set; }
}

@ -8,7 +8,6 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Identity;
namespace Entities
{
@ -16,7 +15,7 @@ namespace Entities
/// Represents an athlete entity in the database.
/// </summary>
[Table("Athlete")]
public class AthleteEntity : IdentityUser
public class AthleteEntity
{
public AthleteEntity() : base() { }
@ -32,35 +31,35 @@ namespace Entities
/// </summary>
[MaxLength(100)]
[Required(ErrorMessage = "Athlete Username is ")]
public string Username { get; set; }
public required string Username { get; set; }
/// <summary>
/// Gets or sets the last name of the athlete.
/// </summary>
[MaxLength(100)]
[Required(ErrorMessage = "Athlete Last Name is ")]
public string LastName { get; set; }
public required string LastName { get; set; }
/// <summary>
/// Gets or sets the first name of the athlete.
/// </summary>
[MaxLength(150)]
[Required(ErrorMessage = "Athlete First Name is ")]
public string FirstName { get; set; }
public required string FirstName { get; set; }
/// <summary>
/// Gets or sets the email of the athlete.
/// </summary>
[MaxLength(100)]
[Required(ErrorMessage = "Athlete Email is ")]
public string Email { get; set; }
public required string Email { get; set; }
/// <summary>
/// Gets or sets the gender of the athlete.
/// </summary>
[MaxLength(1)]
[Required(ErrorMessage = "Athlete Sexe is ")]
public string Sexe { get; set; }
public required string Sexe { get; set; }
/// <summary>
/// Gets or sets the height of the athlete.
@ -76,7 +75,7 @@ namespace Entities
/// Gets or sets the password of the athlete.
/// </summary>
[Required(ErrorMessage = "Athlete Password is ")]
public string Password { get; set; }
public required string Password { get; set; }
/// <summary>
/// Gets or sets the date of birth of the athlete.
@ -90,7 +89,7 @@ namespace Entities
/// </summary>
public bool IsCoach { get; set; }
public byte[] ProfilPicture { get; set; }
public byte[]? ProfilPicture { get; set; }

@ -5,11 +5,4 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.Identity.Stores">
<HintPath>..\..\..\..\..\.nuget\packages\microsoft.extensions.identity.stores\8.0.2\lib\net8.0\Microsoft.Extensions.Identity.Stores.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

@ -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}"
@ -41,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleTestEFMapper", "Test
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFMappers", "EFMappers\EFMappers.csproj", "{9397795D-F482-44C4-8443-A20AC26671AA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APIMappers", "APIMappers\APIMappers.csproj", "{41D18203-1688-43BD-A3AC-FD0C2BD81909}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -86,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
@ -134,13 +130,16 @@ Global
{9397795D-F482-44C4-8443-A20AC26671AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9397795D-F482-44C4-8443-A20AC26671AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9397795D-F482-44C4-8443-A20AC26671AA}.Release|Any CPU.Build.0 = Release|Any CPU
{41D18203-1688-43BD-A3AC-FD0C2BD81909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{41D18203-1688-43BD-A3AC-FD0C2BD81909}.Debug|Any CPU.Build.0 = Debug|Any CPU
{41D18203-1688-43BD-A3AC-FD0C2BD81909}.Release|Any CPU.ActiveCfg = Release|Any CPU
{41D18203-1688-43BD-A3AC-FD0C2BD81909}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{477D2129-A6C9-4FF8-8BE9-5E9E8E5282F8} = {2B227C67-3BEC-4A83-BDA0-F3918FBC0D18}
{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}

@ -1,19 +1,21 @@
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;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Model.Manager;
using Model.Service;
using Model2Entities;
using StubAPI;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace HeartTrackAPI;
public class AppBootstrap(IConfiguration configuration)
@ -24,16 +26,14 @@ public class AppBootstrap(IConfiguration configuration)
{
services.AddControllers();
services.AddEndpointsApiExplorer();
//services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOption>();
AddSwagger(services);
// include Xml comment
// addsecurityRequiment
// securityDef
services.AddSwaggerGen();
AddHeartTrackContextServices(services);
AddModelService(services);
AddIdentityServices(services);
AddApiVersioning(services);
AddSwagger(services);
services.AddHealthChecks();
@ -41,23 +41,58 @@ public class AppBootstrap(IConfiguration configuration)
private void AddHeartTrackContextServices(IServiceCollection services)
{
var connectionString = Configuration.GetConnectionString("HeartTrackAuthConnection");
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new InvalidOperationException("The connection string for the database is not set.");
}
else
string connectionString;
switch (Environment.GetEnvironmentVariable("TYPE"))
{
services.AddDbContext<AuthDbContext>(options => options.UseInMemoryDatabase("AuthDb"));
// builder.Services.AddDbContext<HeartTrackContext>(options => options.UseSqlite(connectionString));
services.AddSingleton<IDataManager, StubData>();
case "BDD":
var HOST = System.Environment.GetEnvironmentVariable("HOST");
var PORT = System.Environment.GetEnvironmentVariable("PORTDB");
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}";
Console.WriteLine(connectionString);
Console.WriteLine("======================");
Console.WriteLine(connectionString);
services.AddSingleton<IDataManager>(provider => new DbDataManager(connectionString));
break;
default:
connectionString = Configuration.GetConnectionString("HeartTrackAuthConnection");
if (string.IsNullOrWhiteSpace(connectionString))
{
services.AddDbContext<AuthDbContext>(options => options.UseInMemoryDatabase("AuthDb"));
//options => options.UseSqlite(connectionString)
//services.AddDbContext<HeartTrackContext>();
services.AddDbContext<HeartTrackContext>(options =>
options.UseSqlite(connectionString), ServiceLifetime.Singleton);
}
break;
}
/*
services.AddSingleton<DbContextOptions<HeartTrackContext>>(provider =>
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<HeartTrackContext>()
.UseSqlite(connection)
.Options;
return options;
});*/
}
private void AddModelService(IServiceCollection services)
{
//services.AddSingleton<IDataManager>(provider => new DbDataManager(provider.GetService<HeartTrackContext>()));
services.AddSingleton<IDataManager, StubData>();
services.AddTransient<IActivityManager, ActivityManager>();
//services.AddTransient<IActivityManager, ActivityManager>();
}
private void AddIdentityServices(IServiceCollection services)
@ -90,25 +125,66 @@ public class AppBootstrap(IConfiguration configuration)
private void AddSwagger(IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.OperationFilter<SwaggerDefaultValues>();
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<string>()
}
};
options.AddSecurityRequirement(scheme);
});
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, SwaggerOptions>();
services.AddSwaggerGen(options =>
{
options.OperationFilter<SwaggerDefaultValues>();
});
/* 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)
{
app.UseHttpsRedirection();
app.MapIdentityApi<IdentityUser>();
app.MapControllers();
app.UseAuthorization();
app.MapIdentityApi<IdentityUser>();
app.MapHealthChecks("/health");

@ -1,24 +1,25 @@
using APIMappers;
using Dto;
using HeartTrackAPI.Request;
using HeartTrackAPI.Responce;
using Microsoft.AspNetCore.Mvc;
using Shared;
using Model;
using Shared;
using Model.Manager;
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;
private readonly ILogger<ActivityController> _logger;
private readonly IActivityManager _activityManager;
public ActivityController(IActivityRepository activityService, ILogger<ActivityController> logger)
public ActivityController(IDataManager dataManager, ILogger<ActivityController> logger)
{
_activityService = activityService;
_activityService = dataManager.ActivityRepo;
_logger = logger;
}
@ -38,9 +39,12 @@ public class ActivityController : Controller
}
_logger.LogInformation("Executing {Action} with parameters: {Parameters}", nameof(GetActivities), pageRequest);
var activities = await _activityService.GetActivities(pageRequest.Index, pageRequest.Count, ActivityOrderCriteria.None, pageRequest.Descending ?? false);
// var pageResponse = new PageResponse<ActivityDto>(pageRequest.Index, pageRequest.Count, totalCount, activities.Select(a => a.ToDto()));
// return Ok(pageResponse);
return Ok();
if(activities == null)
{
return NotFound("No activities found");
}
var pageResponse = new PageResponse<ActivityDto>(pageRequest.Index, pageRequest.Count, totalCount, activities.Select(a => a.ToDto()));
return Ok(pageResponse);
}
catch (Exception e)
{
@ -49,21 +53,63 @@ public class ActivityController : Controller
}
}
/*
public async Task<IActionResult> PostFitFile([FromForm] IFormFile file)
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status415UnsupportedMediaType)]
[MultipartFormData]
[DisableFormValueModelBinding]
public async Task<IActionResult> PostFitFile( Stream file, string contentType) // [FromForm]
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
if (file == null)
{
return BadRequest("No file was provided");
}
var activity = await _activityManager.AddActivityFromFitFile(file);
//var fileUploadSummary = await _fileService.UploadFileAsync(HttpContext.Request.Body, Request.ContentType);
// var activity = await _activityManager.AddActivityFromFitFile(file);
var activity = new Activity
{
Id = 1,
Type = "Running",
Date = new DateTime(2021, 10, 10),
StartTime = new DateTime(2021, 10, 10, 10, 0, 0),
EndTime = new DateTime(2021, 10, 10, 11, 0, 0),
Effort = 3,
Variability = 0.5f,
Variance = 0.5f,
StandardDeviation = 0.5f,
Average = 5.0f,
Maximum = 10,
Minimum = 0,
AverageTemperature = 20.0f,
HasAutoPause = false,
Users =
{
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()
}
}
};
if (activity == null)
{
return BadRequest("The file provided is not a valid fit file");
}
return CreatedAtAction(nameof(GetActivities), activity.ToDto());
return CreatedAtAction(nameof(GetActivity), new { id = activity.Id }, activity.ToDto());
}*/
/*
[HttpGet("{id}")]
public async Task<ActionResult<ActivityDto>> GetActivity(int id)
{
@ -75,18 +121,6 @@ public class ActivityController : Controller
return Ok(activity.ToDto());
}
[HttpPost]
public async Task<ActionResult<ActivityDto>> PostActivity(ActivityDto activityDto)
{
var activity = activityDto.ToModel();
var result = await _activityService.AddActivity(activity);
if (result == null)
{
return BadRequest();
}
return CreatedAtAction(nameof(GetActivity), new { id = result.Id }, result.ToDto());
}
[HttpPut("{id}")]
public async Task<IActionResult> PutActivity(int id, ActivityDto activityDto)
{
@ -112,5 +146,35 @@ public class ActivityController : Controller
return NotFound();
}
return NoContent();
}
/*
public async Task<FileUploadSummary> UploadFileAsync(Stream fileStream, string contentType)
{
var fileCount = 0;
long totalSizeInBytes = 0;
var boundary = GetBoundary(MediaTypeHeaderValue.Parse(contentType));
var multipartReader = new MultipartReader(boundary, fileStream);
var section = await multipartReader.ReadNextSectionAsync();
var filePaths = new List<string>();
var notUploadedFiles = new List<string>();
while (section != null)
{
var fileSection = section.AsFileSection();
if (fileSection != null)
{
totalSizeInBytes += await SaveFileAsync(fileSection, filePaths, notUploadedFiles);
fileCount++;
}
section = await multipartReader.ReadNextSectionAsync();
}
return new FileUploadSummary
{
TotalFilesUploaded = fileCount,
TotalSizeUploaded = ConvertSizeToString(totalSizeInBytes),
FilePaths = filePaths,
NotUploadedFiles = notUploadedFiles
};
}*/
}

@ -1,30 +1,42 @@
using System.ComponentModel.DataAnnotations;
using APIMappers;
using Dto;
using Entities;
using HeartTrackAPI.Request;
using HeartTrackAPI.Responce;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Model;
using Model.Manager;
using Model.Repository;
using Shared;
namespace HeartTrackAPI.Controllers;
/// <summary>
/// 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.
/// </summary>
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/users")]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : Controller
{
private readonly ILogger<UsersController> _logger;
private readonly IActivityRepository _activityService;
private readonly IUserRepository _userService;
// private readonly
public UsersController(ILogger<UsersController> logger, IDataManager dataManager)
{
_logger = logger;
_userService = dataManager.UserRepo;
_activityService = dataManager.ActivityRepo;
}
/// <summary>
/// Récupère une page d'utilisateurs en fonction des critères de pagination et de tri fournis.
/// </summary>
/// <param name="request">Les critères de pagination et de tri pour les utilisateurs.</param>
/// <returns>Une page de données utilisateur selon les critères spécifiés.</returns>
/// <response code="200">Retourne la page demandée d'utilisateurs.</response>
/// <response code="400">La demande de pagination est invalide.</response>
/// <response code="500">Erreur interne du serveur.</response>
[HttpGet]
[ProducesResponseType(typeof(PageResponse<UserDto>), 200)]
[ProducesResponseType(400)]
@ -43,22 +55,29 @@ public class UsersController : Controller
_logger.LogInformation("Executing {Action} with parameters: {Parameters}", nameof(Get), null);
var athletes = await _userService.GetUsers(request.Index, request.Count, Enum.TryParse(request.OrderingPropertyName, out AthleteOrderCriteria result) ? result : AthleteOrderCriteria.None, request.Descending ?? false);
// var pageResponse = new PageResponse<UserDto>(request.Index, request.Count, totalCount, athletes.Select(a => a.ToDto()));
// return Ok(pageResponse);
return Ok();
var pageResponse = new PageResponse<UserDto>(request.Index, request.Count, totalCount, athletes!.Select(a => a.ToDto()));
return Ok(pageResponse);
}
catch (Exception e)
{
_logger.LogError(e, "Error while getting all athletes");
return StatusCode(500);
return Problem();
}
}
/// <summary>
/// Récupère un utilisateur spécifique par son identifiant.
/// </summary>
/// <param name="id">L'identifiant de l'utilisateur à récupérer.</param>
/// <returns>L'utilisateur correspondant à l'identifiant spécifié.</returns>
/// <response code="200">Retourne l'utilisateur demandé.</response>
/// <response code="404">Aucun utilisateur trouvé pour l'identifiant spécifié.</response>
/// <response code="500">Erreur interne du serveur.</response>
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserDto), 200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
public async Task<ActionResult<UserDto>> GetById(int id)
public async Task<ActionResult<UserDto>> GetById([Range(0,int.MaxValue)]int id)
{
try
{
@ -69,17 +88,21 @@ public class UsersController : Controller
_logger.LogError("Athlete with id {id} not found", id);
return NotFound($"Athlete with id {id} not found");
}
// return Ok(athlete.ToDto());
return Ok();
return Ok(athlete.ToDto());
}
catch (Exception e)
{
_logger.LogError(e, "Error while getting athlete by id {id}", id);
return StatusCode(500);
return Problem();
}
}
/// <summary>
/// Obtient le nombre total d'utilisateurs.
/// </summary>
/// <returns>Le nombre total d'utilisateurs.</returns>
/// <response code="200">Retourne le nombre total d'utilisateurs.</response>
/// <response code="500">Erreur interne du serveur.</response>
[HttpGet("count")]
[ProducesResponseType(typeof(int), 200)]
[ProducesResponseType(500)]
@ -94,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();
}
}
/// <summary>
/// Met à jour les informations d'un utilisateur spécifique.
/// </summary>
/// <param name="id">L'identifiant de l'utilisateur à mettre à jour.</param>
/// <param name="user">Les données de l'utilisateur pour la mise à jour.</param>
/// <returns>L'utilisateur mis à jour.</returns>
/// <response code="200">Retourne l'utilisateur mis à jour.</response>
/// <response code="404">Utilisateur non trouvé.</response>
/// <response code="500">Erreur interne du serveur.</response>
[HttpPut("{id}")]
[ProducesResponseType(typeof(UserDto), 200)]
[ProducesResponseType(404)]
@ -113,23 +145,30 @@ public class UsersController : Controller
_logger.LogError("Athlete with id {id} not found", id);
return NotFound($"Athlete with id {id} not found");
}
// var updatedAthlete = await _userService.UpdateItem(id, user.ToModel());
// if(updatedAthlete == null)
// {
// _logger.LogError("Error while updating athlete with id {id}", id);
// return StatusCode(500);
// }
// return Ok(updatedAthlete.ToDto());
return Ok();
var updatedAthlete = await _userService.UpdateItem(id, user.ToModel());
if(updatedAthlete == null)
{
_logger.LogError("Error while updating athlete with id {id}", id);
return Problem();
}
return Ok(updatedAthlete.ToDto());
}
catch (Exception e)
{
_logger.LogError(e, "Error while getting the number of users");
return StatusCode(500);
return Problem();
}
}
/// <summary>
/// Supprime un utilisateur spécifique.
/// </summary>
/// <param name="id">L'identifiant de l'utilisateur à supprimer.</param>
/// <returns>Action result.</returns>
/// <response code="200">Utilisateur supprimé avec succès.</response>
/// <response code="404">Utilisateur non trouvé.</response>
/// <response code="500">Erreur interne du serveur.</response>
[HttpDelete("{id}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
@ -151,18 +190,68 @@ 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();
}
}
/// <summary>
/// Obtient la liste des amis d'un utilisateur spécifique.
/// </summary>
/// <param name="id">L'identifiant de l'utilisateur.</param>
/// <param name="request">Les critères de pagination et de tri.</param>
/// <returns>La liste paginée des amis.</returns>
/// <response code="200">Retourne la liste paginée des amis de l'utilisateur.</response>
/// <response code="404">Utilisateur non trouvé.</response>
/// <response code="500">Erreur interne du serveur.</response>
[HttpGet("{id}/friends")]
[ProducesResponseType(typeof(PageResponse<UserDto>), 200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
public async Task<ActionResult<PageResponse<UserDto>>> GetFriends(int id, [FromQuery] PageRequest request)
{
try
{
_logger.LogInformation("Executing {Action} with parameters: {Parameters} for {Id}", nameof(GetFriends), null,id);
var athlete = await _userService.GetItemById(id);
if (athlete == null)
{
_logger.LogError("Athlete with id {id} not found", id);
return NotFound($"Athlete with id {id} not found");
}
var totalCount = await _userService.GetNbFriends(athlete);
if (request.Count * request.Index >= totalCount)
{
_logger.LogError("To many object is asked the max is {totalCount} but the request is superior of ", totalCount);
return BadRequest("To many object is asked the max is : " + totalCount);
}
var friends = await _userService.GetFriends(athlete, request.Index, request.Count, Enum.TryParse(request.OrderingPropertyName, out AthleteOrderCriteria result) ? result : AthleteOrderCriteria.None, request.Descending ?? false);
if (friends == null) return NotFound();
var pageResponse = new PageResponse<UserDto>(request.Index, request.Count, totalCount, friends.Select(a => a.ToDto()));
return Ok(pageResponse);
}
catch (Exception e)
{
_logger.LogError(e, "Error while getting the number of users");
return Problem();
}
}
/// <summary>
/// Ajoute un ami à un utilisateur spécifique.
/// </summary>
/// <param name="id">L'identifiant de l'utilisateur.</param>
/// <param name="friendId">L'identifiant de l'ami à ajouter.</param>
/// <returns>Action result.</returns>
/// <response code="200">Ami ajouté avec succès.</response>
/// <response code="404">Utilisateur ou ami non trouvé.</response>
/// <response code="500">Erreur interne du serveur.</response>
[HttpPost("{id}/friend/{friendId}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
@ -188,17 +277,160 @@ 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();
}
}
/// <summary>
/// Supprime un ami d'un utilisateur spécifique.
/// </summary>
/// <param name="id">L'identifiant de l'utilisateur.</param>
/// <param name="friendId">L'identifiant de l'ami à supprimer.</param>
/// <returns>Action result.</returns>
/// <response code="200">Ami supprimé avec succès.</response>
/// <response code="404">Utilisateur ou ami non trouvé.</response>
/// <response code="500">Erreur interne du serveur.</response>
[HttpDelete("{id}/friend/{friendId}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
public async Task<IActionResult> RemoveFriend(int id, int friendId)
{
try
{
_logger.LogInformation("Executing {Action} with parameters: {Parameters} for {Id}", nameof(RemoveFriend), friendId,id);
var athlete = await _userService.GetItemById(id);
if (athlete == null)
{
_logger.LogError("Athlete with id {id} not found", id);
return NotFound($"Athlete with id {id} not found");
}
var friend = await _userService.GetItemById(friendId);
if (friend == null)
{
_logger.LogError("Athlete with id {id} not found", friendId);
return NotFound($"Athlete with id {friendId} not found");
}
var isRemoved = await _userService.RemoveFriend(athlete, friend);
if(!isRemoved)
{
_logger.LogError("Error while removing friend with id {friendId} to athlete with id {id}", friendId, id);
return Problem();
}
return Ok();
}
catch (Exception e)
{
_logger.LogError(e, "Error while getting the number of users");
return Problem();
}
}
// #[TODO] [Dave] ou faire un get qui si le role est coach resend les athletes et si le role est athlete resend les coach
/// <summary>
/// Obtient la liste des athlètes d'un coach spécifique.
/// </summary>
/// <param name="coachId">L'identifiant du coach.</param>
/// <param name="request">Les critères de pagination et de tri.</param>
/// <returns>La liste paginée des athlètes.</returns>
/// <response code="200">Retourne la liste paginée des athlètes du coach.</response>
/// <response code="404">Coach non trouvé.</response>
/// <response code="500">Erreur interne du serveur.</response>
[HttpGet("{coachId}/athletes")]
[ProducesResponseType(typeof(PageResponse<UserDto>), 200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
public async Task<ActionResult<PageResponse<UserDto>>> GetAthletes(int coachId, [FromQuery] PageRequest request)
{
try
{
_logger.LogInformation("Executing {Action} with parameters: {Parameters} for {Id}", nameof(GetAthletes), null,coachId);
var coach = await _userService.GetItemById(coachId);
if (coach == null)
{
_logger.LogError("Athlete with id {id} not found", coachId);
return NotFound($"Athlete with id {coachId} not found");
}
var totalCount = await _userService.GetNbFriends(coach);
if (request.Count * request.Index >= totalCount)
{
_logger.LogError("To many object is asked the max is {totalCount} but the request is superior of ", totalCount);
return BadRequest("To many object is asked the max is : " + totalCount);
}
var athletes = await _userService.GetFriends(coach, request.Index, request.Count, Enum.TryParse(request.OrderingPropertyName, out AthleteOrderCriteria result) ? result : AthleteOrderCriteria.None, request.Descending ?? false);
if (athletes == null) return NotFound();
var pageResponse = new PageResponse<UserDto>(request.Index, request.Count, totalCount, athletes.Select(a => a.ToDto()));
return Ok(pageResponse);
}
catch (Exception e)
{
_logger.LogError(e, "Error while getting the number of users");
return Problem();
}
}
/// <summary>
/// Obtient la liste des activités d'un utilisateur spécifique.
/// </summary>
/// <param name="userId">L'identifiant de l'utilisateur.</param>
/// <param name="pageRequest">Les critères de pagination et de tri.</param>
/// <returns>La liste paginée des activités de l'utilisateur.</returns>
/// <response code="200">Retourne la liste paginée des activités.</response>
/// <response code="404">Aucune activité trouvée.</response>
/// <response code="500">Erreur interne du serveur.</response>
[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<ActionResult<PageResponse<ActivityDto>>> GetActivitiesByUser(int userId, [FromQuery] PageRequest pageRequest)
{
try
{
var totalCount = await _activityService.GetNbActivitiesByUser(userId);
if (pageRequest.Count * pageRequest.Index >= totalCount)
{
_logger.LogError("To many object is asked the max is {totalCount} but the request is superior of ", totalCount);
return BadRequest("To many object is asked the max is : " + totalCount);
}
_logger.LogInformation("Executing {Action} with parameters: {Parameters}", nameof(GetActivitiesByUser), pageRequest);
var activities = await _activityService.GetActivitiesByUser(userId, pageRequest.Index, pageRequest.Count, ActivityOrderCriteria.None, pageRequest.Descending ?? false);
if(activities == null)
{
return NotFound("No activities found");
}
var pageResponse = new PageResponse<ActivityDto>(pageRequest.Index, pageRequest.Count, totalCount, activities.Select(a => a.ToDto()));
return Ok(pageResponse);
}
catch (Exception e)
{
_logger.LogError(e, "Error while getting all activities");
return Problem();
}
}
/* [TODO] [Dave]
[HttpGet("{userId}/trainings")]
[ProducesResponseType(typeof(PageResponse<TrainingDto>), 200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
public async Task<ActionResult<PageResponse<TrainingDto>> GetTrainings(int userId, [FromQuery] PageRequest request)
*/
/// <summary>
/// Déconnecte l'utilisateur actuel.
/// </summary>
/// <param name="signInManager">Le gestionnaire de connexion.</param>
/// <param name="empty">Paramètre vide utilisé pour s'assurer que la requête provient bien d'un client.</param>
/// <returns>Action result.</returns>
/// <response code="200">Déconnexion réussie.</response>
/// <response code="401">Déconnexion non autorisée.</response>
/// <response code="500">Erreur interne du serveur.</response>
[HttpPost("logout")]
[ProducesResponseType(200)]
[ProducesResponseType(401)]

@ -12,15 +12,27 @@ COPY ["StubbedContextLib/StubbedContextLib.csproj", "StubbedContextLib/"]
COPY ["Shared/Shared.csproj", "Shared/"]
COPY ["Entities/Entities.csproj", "Entities/"]
COPY ["Dto/Dto.csproj", "Dto/"]
COPY ["ApiMappeur/ApiMappeur.csproj", "ApiMappeur/"]
COPY ["APIMappers/APIMappers.csproj", "APIMappers/"]
COPY ["EFMappers/EFMappers.csproj", "EFMappers/"]
COPY ["DbContextLib/DbContextLib.csproj", "DbContextLib/"]
COPY ["Model/Model.csproj", "Model/"]
COPY ["Model2Entities/Model2Entities.csproj", "Model2Entities/"]
COPY ["StubAPI/StubAPI.csproj", "StubAPI/"]
COPY ["StubbedContextLib/StubbedContextLib.csproj", "StubbedContextLib/"]
RUN dotnet restore "HeartTrackAPI/HeartTrackAPI.csproj"
COPY . .
RUN echo $DOTNET_ROOT
RUN echo SHOULDDOTNET_ROOT
RUN ls
RUN dotnet tool install --global dotnet-ef --version 8.0
ENV PATH="${PATH}:/root/.dotnet/tools"
# Add the migrations
RUN dotnet ef migrations add --project StubbedContextLib/ --startup-project HeartTrackAPI/ --context StubbedContextLib.TrainingStubbedContext --configuration Debug Initial --output-dir Migrations
# Update the database
RUN dotnet ef database update --project StubbedContextLib/ --startup-project HeartTrackAPI/ --context StubbedContextLib.TrainingStubbedContext --configuration Debug
WORKDIR "/src/HeartTrackAPI"
RUN ls
RUN dotnet build "HeartTrackAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build

@ -0,0 +1,44 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace HeartTrackAPI;
public class FileUploadSummary
{
public int TotalFilesUploaded { get; set; }
public string TotalSizeUploaded { get; set; }
public IList<string> FilePaths { get; set; } = new List<string>();
public IList<string> NotUploadedFiles { get; set; } = new List<string>();
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class MultipartFormDataAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var request = context.HttpContext.Request;
if (request.HasFormContentType
&& request.ContentType.StartsWith("multipart/form-data", StringComparison.OrdinalIgnoreCase))
{
return;
}
context.Result = new StatusCodeResult(StatusCodes.Status415UnsupportedMediaType);
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}

@ -5,6 +5,8 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -12,23 +14,22 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.1.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ApiMappeur\ApiMappeur.csproj" />
<ProjectReference Include="..\APIMappers\APIMappers.csproj" />
<ProjectReference Include="..\DbContextLib\DbContextLib.csproj" />
<ProjectReference Include="..\Dto\Dto.csproj" />
<ProjectReference Include="..\Model2Entities\Model2Entities.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
<ProjectReference Include="..\StubAPI\StubAPI.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json" Version="13.0.1">
<HintPath>..\..\..\..\..\.nuget\packages\newtonsoft.json\13.0.1\lib\netstandard2.0\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

@ -1,6 +0,0 @@
@HeartTrackAPI_HostAddress = http://localhost:5030
GET {{HeartTrackAPI_HostAddress}}/weatherforecast/
Accept: application/json
###

@ -1,16 +1,13 @@
using DbContextLib;
using DbContextLib.Identity;
using Entities;
using HeartTrackAPI;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using Model.Manager;
using StubAPI;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddConsole();
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.Limits.MaxRequestBodySize = long.MaxValue;
});
var init = new AppBootstrap(builder.Configuration);
@ -19,5 +16,7 @@ init.ConfigureServices(builder.Services);
var app = builder.Build();
init.Configure(app, app.Environment);
// app?.Services?.GetService<HeartTrackContext>()?.Database.EnsureCreated();
app.Services.GetService<HeartTrackContext>()!.Database.EnsureCreated();
app.Run();

@ -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;
}

@ -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;
}
}
}

@ -35,7 +35,7 @@ public class SwaggerOptions: IConfigureNamedOptions<SwaggerGenOptions>
/// </summary>
/// <param name="name"></param>
/// <param name="options"></param>
public void Configure(string name, SwaggerGenOptions options)
public void Configure(string? name, SwaggerGenOptions options)
{
Configure(options);
}
@ -50,8 +50,12 @@ public class SwaggerOptions: IConfigureNamedOptions<SwaggerGenOptions>
{
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)

@ -4,5 +4,8 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"HeartTrackAuthConnection": "Data Source=uca.HeartTrack.db"
}
}

@ -31,7 +31,8 @@ public class Activity
public int Minimum { get; set; }
public float AverageTemperature { get; set; }
public bool HasAutoPause { get; set; }
public HashSet<User> Users { get; private set; } = new HashSet<User>();
public Activity(int idActivity ,string type, DateTime date, DateTime startTime, DateTime endTime,
int effort, float variability, float variance, float standardDeviation,
float average, int maximum, int minimum, float averageTemperature, bool hasAutoPause)

@ -1,4 +1,3 @@
using Microsoft.AspNetCore.Http;
namespace Model.Manager;
public interface IActivityManager

@ -7,18 +7,12 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Entities\Entities.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
<ProjectReference Include="..\Entities\Entities.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.2" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Identity">
<HintPath>..\..\..\..\..\.dotnet\shared\Microsoft.AspNetCore.App\8.0.1\Microsoft.AspNetCore.Identity.dll</HintPath>
</Reference>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.2" />
</ItemGroup>
</Project>

@ -4,10 +4,12 @@ namespace Model.Repository;
public interface IActivityRepository
{
public Task<IEnumerable<Activity>> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false);
public Task<IEnumerable<Activity>?> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false);
public Task<Activity?> GetActivityByIdAsync(int id);
public Task<Activity?> AddActivity(Activity activity);
public Task<Activity?> UpdateActivity(int id, Activity activity);
public Task<bool> DeleteActivity(int id);
public Task<int> GetNbItems();
public Task<IEnumerable<Activity>?> GetActivitiesByUser(int userId, int index, int count, ActivityOrderCriteria orderCriteria, bool descending= false);
public Task<int> GetNbActivitiesByUser(int userId);
}

@ -4,7 +4,14 @@ namespace Model.Repository;
public interface IUserRepository : IGenericRepository<User>
{
public Task<IEnumerable<User>> GetUsers(int index, int count, AthleteOrderCriteria? criteria , bool descending = false);
public Task<IEnumerable<User>?> GetUsers(int index, int count, AthleteOrderCriteria? criteria , bool descending = false);
public Task<bool> AddFriend(User user, User friend);
public Task<bool> RemoveFriend(User user, User friend);
// should be removed cause i just have to call the GetItem then get the friends
public Task<IEnumerable<User>?> GetFriends(User user, int index, int count, AthleteOrderCriteria? criteria, bool descending = false);
public Task<int> GetNbFriends(User user);
}

@ -1,31 +0,0 @@
using Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
namespace Model.Service;
public class EmailSender : IEmailSender<AthleteEntity>
{
private IEmailSender<AthleteEntity> _emailSenderImplementation;
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
{
throw new NotImplementedException();
}
public async Task SendConfirmationLinkAsync(AthleteEntity user, string email, string confirmationLink)
{
throw new NotImplementedException();
}
public async Task SendPasswordResetLinkAsync(AthleteEntity user, string email, string resetLink)
{
throw new NotImplementedException();
}
public async Task SendPasswordResetCodeAsync(AthleteEntity user, string email, string resetCode)
{
throw new NotImplementedException();
}
}

@ -38,7 +38,7 @@ public partial class DbDataManager : IDataManager
{
_logger.LogInformation($"GetActivityByIdAsync with id {id}", id);
// ! By None don't pass the filter
var activity = _dataManager.DbContext.ActivitiesSet.GetItemsWithFilterAndOrdering(b => b.IdActivity == id, 0, 1, ActivityOrderCriteria.ByType, false).First().ToModel();
var activity = _dataManager.DbContext.ActivitiesSet.GetItemsWithFilterAndOrdering(b => b.IdActivity == id, 0, 1, ActivityOrderCriteria.None, false).First().ToModel();
if (activity != null)
_logger.LogInformation($"Retrieved activity with ID {id}");
@ -136,5 +136,15 @@ public partial class DbDataManager : IDataManager
throw;
}
}
public Task<IEnumerable<Activity>> GetActivitiesByUser(int userId, int index, int count, ActivityOrderCriteria criteria, bool descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbActivitiesByUser(int userId)
{
throw new NotImplementedException();
}
}
}

@ -18,9 +18,17 @@ public partial class DbDataManager: IDataManager
DbContext = dbContext;
ActivityRepo = new ActivityRepository(this);
UserRepo = new UserRepository(this);
DbContext.Database.EnsureCreated();
ActivityMapper.Reset();
// Faire pour les autres reset() des autres mappers
}
public DbDataManager(string dbPlatformPath)
: this(new HeartTrackContext(dbPlatformPath))
{
DbContext.Database.EnsureCreated();
}
public DbDataManager()
{

@ -7,28 +7,25 @@ namespace Model2Entities;
public static class Extensions
{
internal static Task<T?> AddItem<T>(this HeartTrackContext context, T? item) where T :class
internal static async Task<T?> AddItem<T>(this HeartTrackContext context, T? item) where T :class
{
if(item == null || context.Set<T>().Contains(item))
{
return Task.FromResult<T?>(default(T));
return await Task.FromResult<T?>(null);
}
context.Set<T>().Add(item);
context.SaveChangesAsync();
var entry = context.Set<T>().Add(item);
await context.SaveChangesAsync();
return Task.FromResult<T?>(item);
return await Task.FromResult<T?>(entry.Entity);
}
internal static Task<bool> DeleteItem<T>(this HeartTrackContext context, int? id) where T:class
internal static async Task<bool> DeleteItem<T>(this HeartTrackContext context, int? id) where T:class
{
var item = context.Set<T>().Find(id);
if(item == null)
{
return Task.FromResult(false);
}
var item = await context.Set<T>().FindAsync(id);
if(item == null) return await Task.FromResult(false);
context.Set<T>().Remove(item);
context.SaveChangesAsync();
return Task.FromResult(true);
await context.SaveChangesAsync();
return await Task.FromResult(true);
}
// ! 1
@ -81,10 +78,11 @@ public static class Extensions
{
var filteredList = list.Where(filter);
if(orderCriterium != null)
if(orderCriterium != null && orderCriterium.ToString() != "None")
{
filteredList = filteredList.OrderByCriteria(orderCriterium, descending);
}
}
return filteredList
.Skip(index * count)
.Take(count);

@ -10,6 +10,14 @@
<ProjectReference Include="..\DbContextLib\DbContextLib.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
<ProjectReference Include="..\EFMappers\EFMappers.csproj" />
<ProjectReference Include="..\StubbedContextLib\StubbedContextLib.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

@ -54,5 +54,20 @@ public partial class DbDataManager
{
throw new NotImplementedException();
}
public async Task<bool> RemoveFriend(User user, User friend)
{
throw new NotImplementedException();
}
public Task<IEnumerable<User>?>? GetFriends(User user, int index, int count, AthleteOrderCriteria? criteria, bool descending = false)
{
throw new NotImplementedException();
}
public Task<int> GetNbFriends(User user)
{
throw new NotImplementedException();
}
}
}

@ -2,7 +2,7 @@ namespace Shared;
public static class Extensions
{
public static U? ToU<T, U>(this T t, GenericMapper<T, U> mapper, Func<T, U> func) where U :class where T :class
public static U ToU<T, U>(this T t, GenericMapper<T, U> mapper, Func<T, U> func) where U :class where T :class
{
var u = mapper.GetU(t);
if (u != null) {
@ -14,7 +14,7 @@ public static class Extensions
return u;
}
// , Action<T, U> action
public static T? ToT<T,U>(this U u, GenericMapper<T, U> mapper, Func<U, T> func) where U :class where T :class
public static T ToT<T,U>(this U u, GenericMapper<T, U> mapper, Func<U, T> func) where U :class where T :class
{
var t = mapper.GetT(u);
if (t != null) {

@ -6,33 +6,73 @@ namespace StubAPI;
public class ActivityService: IActivityRepository
{
public async Task<IEnumerable<Activity>> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false)
{
throw new NotImplementedException();
}
private List<Activity> _activities = new List<Activity>(
new Activity[]
{
new Activity
{
Id = 1,
Type = "Running",
Date = new DateTime(2021, 10, 10),
StartTime = new DateTime(2021, 10, 10, 10, 0, 0),
EndTime = new DateTime(2021, 10, 10, 11, 0, 0),
Effort = 3,
Variability = 0.5f,
Variance = 0.5f,
StandardDeviation = 0.5f,
Average = 5.0f,
Maximum = 10,
Minimum = 0,
AverageTemperature = 20.0f,
HasAutoPause = false,
Users = {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()
}}
},
}
);
public async Task<IEnumerable<Activity>?> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false)
=> await Task.FromResult(_activities.GetItemsWithFilterAndOrdering(c=>true,index, count,criteria != ActivityOrderCriteria.None ? criteria: null , descending));
public async Task<Activity?> GetActivityByIdAsync(int id)
public Task<Activity?> GetActivityByIdAsync(int id)
{
throw new NotImplementedException();
return Task.FromResult(_activities.FirstOrDefault(s => s.Id == id));
}
public async Task<Activity?> AddActivity(Activity activity)
public Task<Activity?> AddActivity(Activity activity)
=> _activities.AddItem(activity);
public async Task<Activity?> UpdateActivity(int id, Activity activity)
{
throw new NotImplementedException();
var oldActivity = _activities.FirstOrDefault(s => s.Id == id);
if (oldActivity == null) return null;
return await _activities.UpdateItem(oldActivity, activity);
}
public async Task<Activity?> UpdateActivity(int id, Activity activity)
public Task<bool> DeleteActivity(int id)
{
throw new NotImplementedException();
var activity = _activities.FirstOrDefault(s => s.Id == id);
if (activity == null) return Task.FromResult(false);
return _activities.DeleteItem(activity);
}
public async Task<bool> DeleteActivity(int id)
public Task<int> GetNbItems()
=> Task.FromResult(_activities.Count);
public async Task<IEnumerable<Activity>?> GetActivitiesByUser(int userId, int index, int count, ActivityOrderCriteria criteria, bool descending = false)
{
throw new NotImplementedException();
var activities = _activities.GetItemsWithFilterAndOrdering(c => c.Users.Any(u => u.Id == userId), index, count,
criteria != ActivityOrderCriteria.None ? criteria : null, descending);
return await Task.FromResult(activities);
}
public async Task<int> GetNbItems()
public Task<int> GetNbActivitiesByUser(int userId)
{
throw new NotImplementedException();
return Task.FromResult(_activities.Count(a => a.Users.Any(u => u.Id == userId)));
}
}

@ -51,14 +51,36 @@ public class UserService : IUserRepository
return true;
}
public async Task<IEnumerable<User>> GetItems(int index, int count, string? orderingProperty = null,
bool descending = false)
public async Task<bool> RemoveFriend(User user, User friend)
{
if (user == null || friend == null)
{
return false;
}
if (!user.Users.Contains(friend))
{
return false;
}
return await this.GetUsers(index, count, this.ToEnum(orderingProperty), descending);
user.Users.Remove(friend);
return true;
}
public async Task<IEnumerable<User>?>? GetFriends(User user, int index, int count, AthleteOrderCriteria? criteria, bool descending = false)
=>await Task.FromResult(athletes.FirstOrDefault(s => s.Id == user.Id)?.Users.GetItemsWithFilterAndOrdering(c=>true,index, count,criteria, descending));
public Task<int> GetNbFriends(User user)
{
return Task.FromResult(athletes.FirstOrDefault(s => s.Id == user.Id)?.Users.Count ?? 0);
}
public async Task<IEnumerable<User>> GetItems(int index, int count, string? orderingProperty = null,
bool descending = false)
=>await GetUsers(index, count, this.ToEnum(orderingProperty), descending);
public async Task<User?> GetItemById(int id)
=>await Task.FromResult(athletes.FirstOrDefault(s => s.Id == id));

@ -39,13 +39,15 @@ public static class Extensions
public static IEnumerable<T> GetItemsWithFilterAndOrdering<T>(this IEnumerable<T> list, Func<T, bool> filter, int index, int count, Enum? orderCriterium, bool descending = false ) where T : class
{
var filteredList = list.Where(filter);
IEnumerable<T> query = list;
query = query.Where(filter);
if(orderCriterium != null)
{
filteredList = filteredList.OrderByCriteria(orderCriterium, descending);
query = query.OrderByCriteria(orderCriterium, descending);
}
return filteredList
return query
.Skip(index * count)
.Take(count);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\DbContextLib\DbContextLib.csproj" />
<ProjectReference Include="..\Entities\Entities.csproj" />
<ItemGroup>
<ProjectReference Include="..\DbContextLib\DbContextLib.csproj" />
<ProjectReference Include="..\Entities\Entities.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>

@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

@ -1,10 +0,0 @@
namespace TestsXUnit;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}

@ -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<IDataManager> _dataManagerMock;
private IDataManager _dataManager;
private UsersController _usersController;
private readonly List<User> _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<IDataManager>();
_dataManagerMock.Setup(dm => dm.UserRepo.GetNbItems()).ReturnsAsync(_users.Count);
_dataManagerMock.Setup(dm =>
dm.UserRepo.GetUsers(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<AthleteOrderCriteria>(),
It.IsAny<bool>())).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<UsersController>(), _dataManagerMock.Object);
}
/*
[TestInitialize]
public void SetUp()
{
_dataManager = new StubData();
_usersController = new UsersController(new NullLogger<UsersController>(), _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<UserDto>));
var pageResponse = okResult.Value as PageResponse<UserDto>;
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<UserDto>));
var pageResponse = okResult.Value as PageResponse<UserDto>;
Assert.IsNotNull(pageResponse);
Assert.AreEqual(expectedItemCount, pageResponse.Items.Count());
}
[TestMethod]
public async Task Get_ReturnsInternalServerError_OnException()
{
_dataManagerMock.Setup(dm =>
dm.UserRepo.GetUsers(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<AthleteOrderCriteria>(),
It.IsAny<bool>()))
.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));
}
}

@ -10,7 +10,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.4"/>
<PackageReference Include="MSTest.TestFramework" Version="3.0.4"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>

@ -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<UsersController>(), 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<UserDto>));
}
/*
[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",
};
}*/
}
Loading…
Cancel
Save