diff --git a/.drone.yml b/.drone.yml
index ff39178..4a7087f 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -2,12 +2,11 @@ kind: pipeline
type: docker
name: HeartTrack-API
-trigger:
- branch:
- - master
- event:
- - push
-
+#trigger:
+# branch:
+# - master
+# event:
+# - push
steps:
- name: build
image: mcr.microsoft.com/dotnet/sdk:8.0
@@ -22,7 +21,7 @@ steps:
commands:
- cd src/
- dotnet restore HeartTrack.sln
- - dotnet test HeartTrack.sln --no-restore
+ - dotnet test HeartTrack.sln --no-restore
depends_on: [build]
- name: code-analysis
@@ -45,45 +44,44 @@ steps:
- 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: [ tests ]
-
+
- name: swagger
- image: mcr.microsoft.com/dotnet/sdk:8.0
+ image: mcr.microsoft.com/dotnet/sdk:7.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
+ 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
+ - 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"
+ - 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
+ - /entrypoint.sh -l docs/doxygen -t doxygen
when:
event:
- push
depends_on: [ build ]
volumes:
-- name: docs
- temp: {}
-
----
+ - name: docs
+ temp: {}
+---
kind: pipeline
type: docker
@@ -92,69 +90,98 @@ name: HeartTrack-API-CD
trigger:
event:
- push
-steps:
+steps:
- name: docker-build-and-push
image: plugins/docker
settings:
- dockerfile: src/HeartTrackAPI/Dockerfile
- context: src/
- registry: hub.codefirst.iut.uca.fr
- repo: hub.codefirst.iut.uca.fr/david.d_almeida/api
- username:
- from_secret: SECRET_REGISTRY_USERNAME
- password:
- from_secret: SECRET_REGISTRY_PASSWORD
-
+ dockerfile: src/HeartTrackAPI/Dockerfile
+ context: src/
+ registry: hub.codefirst.iut.uca.fr
+ repo: hub.codefirst.iut.uca.fr/david.d_almeida/api
+ username:
+ from_secret: SECRET_REGISTRY_USERNAME
+ password:
+ from_secret: SECRET_REGISTRY_PASSWORD
+
# 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
- ADMINS: davidd_almeida,kevinmonteiro,antoineperederii,paullevrault,antoinepinagot,nicolasraymond,marcchevaldonne
- OVERWRITE: true
+ CODEFIRST_CLIENTDRONE_ENV_TYPE: STUB
+ IMAGENAME: hub.codefirst.iut.uca.fr/david.d_almeida/api:latest
+ CONTAINERNAME: heart_stub
+ COMMAND: create
+ ADMINS: davidd_almeida,kevinmonteiro,antoineperederii,paullevrault,antoinepinagot,nicolasraymond,camillepetitalot
+ 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 ]
+
# 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: true
- PRIVATE: false
- 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
- ADMINS: davidd_almeida,kevinmonteiro,antoineperederii,paullevrault,antoinepinagot,nicolasraymond,camillepetitalot
-
-
- # database container bdd
+ IMAGENAME: mariadb:10
+ CONTAINERNAME: mysql
+ COMMAND: create
+ 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
+ ADMINS: davidd_almeida,kevinmonteiro,antoineperederii,paullevrault,antoinepinagot,nicolasraymond,camillepetitalot
+
+
+ # 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: HeartDev-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,nicolasraymond,camillepetitalot
- COMMAND: create
- OVERWRITE: true
- depends_on: [deploy-container-mysql, docker-build-and-push, deploy-container-stub]
\ No newline at end of file
+ CODEFIRST_CLIENTDRONE_ENV_TYPE: BDD
+ CODEFIRST_CLIENTDRONE_ENV_HOST: HeartDev-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,nicolasraymond
+ COMMAND: create
+ OVERWRITE: true
+ depends_on: [deploy-container-mysql, docker-build-and-push, deploy-container-stub]
+
+ - name: deploy-container-phpmyadmin
+ image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest
+ environment:
+ IMAGENAME: phpmyadmin/phpmyadmin
+ CONTAINERNAME: phpmyadmin
+ COMMAND: create
+ OVERWRITE: true
+ CODEFIRST_CLIENTDRONE_ENV_PMA_HOST: HeartDev-mysql
+ CODEFIRST_CLIENTDRONE_ENV_PMA_PORT: 3306
+ # CODEFIRST_CLIENTDRONE_ENV_PMA_ABSOLUTE_URI: /containers/HeartDev-phpmyadmin
+ CODEFIRST_CLIENTDRONE_ENV_PMA_USER:
+ from_secret: db_user
+ CODEFIRST_CLIENTDRONE_ENV_PMA_PASSWORD:
+ from_secret: db_password
+ # CODEFIRST_CLIENTDRONE_ENV_PMA_ABSOLUTE_URI: /phpmyadmin
+ ADMINS: davidd_almeida,kevinmonteiro,antoineperederii,paullevrault,antoinepinagot,nicolasraymond
+ depends_on: [deploy-container-mysql]
\ No newline at end of file
diff --git a/README.md b/README.md
index e912c58..0fc39ec 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,11 @@
+
+
---


@@ -29,7 +31,7 @@
# Table des matières
-[Présentation](#présentation) | [Répartition du Git](#répartition-du-git) | [Documentation](#documentation) | [Prérequis](#prerequisites) | [Pour commencer](#getting-started) | [Ce que nous avons fait](#ce-que-nous-avons-fait) | [Fabriqué avec](#fabriqué-avec) | [Contributeurs](#contributeurs) | [Comment contribuer](#comment-contribuer) | [License](#license) | [Remerciements](#remerciements)
+[Présentation](#présentation) | [Répartition du Git](#répartition-du-git) | [Documentation](#documentation) | [Prerequisites](#prerequisites) | [Getting Started](#getting-started) | [Features](#features) | [Ce que nous avons fait](#ce-que-nous-avons-fait) | [Fabriqué avec](#fabriqué-avec) | [Contributeurs](#contributeurs) | [Comment contribuer](#comment-contribuer) | [License](#license) | [Remerciements](#remerciements)
@@ -40,6 +42,7 @@
### Contexte
HeartTrack est une application web PHP et mobile Android destinée aux sportifs et aux coachs afin de permettre l'analyse de courbes de fréquences cardiaques et le suivi d'équipe sportive. L'objectif principal de cette application est de récupérer les données de fréquence cardiaque à partir de fichiers .FIT, de les afficher sous forme de courbes, d'identifier des paternes, de fournir des statistiques et de réaliser des prédictions liées à l'effort physique, à la chaleur, à la récupération, etc.
+!! Fuseaux horaires de base
### Récapitulatif du Projet
@@ -48,10 +51,12 @@ Le projet HeartTrack, avec son application HeartTrack, vise à offrir une soluti
## Répartition du Git
-[**Sources**](src/) : **Code de l'application**
+[**Sources**](Sources/) : **Code de l'application**
[**Documents**](docs/Diagramme/README_DIAGRAMMES.md) : **Documentation de l'application et diagrammes**
+[**Wiki**](https://codefirst.iut.uca.fr/git/HeartDev/Web/wiki/PHP) : **Wiki de notre projet (attendus PHP)**
+
---
Le projet HeartTrack utilise un modèle de flux de travail Git (Gitflow) pour organiser le développement. Voici une brève explication des principales branches :
@@ -76,56 +81,54 @@ Documentation et informations à propos de `HearthTrack` disponible [ici](https:
* [](https://git-scm.com/)
## Getting Started
-Lancer le projet via le projet HeartTrackAPI afin de démarrer la base de donnée pour swagger en http.
-Les méthodes PUT sont en cours de production. Les autres routes sont finalisés et en cours de tests.
## Ce que nous avons fait
### Entity Framework
réalisé | niveau | description | coeff | jalon
--- | --- | --- | --- | ---
-✅ | ☢️ | Le dépôt doit être accessible par l'enseignant | ☢️ | J1
-✅ | ☢️ | un .gitignore doit exister au premier push | ☢️ | J1
-✅ | 🎬 | les *projets* et les tests compilent | 1 | J1 & J2
-✅ | 🎬 | le projet et le tests s'exécutent sans bug (concernant la partie persistance) | 3 | J1 & J2
-✅ | 🟢 | Transcription du modèle : Modèle vers entités (et inversement) | 2 | J1
-✅ | 🟢 | Requêtes CRUD simples (sur une table) | 1 | J1
-✅ | 🟢 | Utilisation de LINQ to Entities | 2 | J1
-✅ | 🟡 | Injection / indépendance du fournisseur | 1 | J1
-✅ | 🟡 | Requêtes CRUD sur des données complexes (images par exemple) | 2 | J1
-✅ | 🟢 | Tests - Appli Console | 1 | J1
-✅ | 🟢 | Tests - Tests unitaires (avec SQLite in memory) | 2 | J1
-✅ | 🟢 | Tests - Données stubbées et/ou Moq | 1 | J1
-✅ | 🟡 | CI : build, tests, Sonar (doc?) | 1 | J1
-✅ | 🟡 | Utilisation de relations (One-to-One, One-to-Many, Many-to-Many) (+ mapping, TU, Requêtes) | 4 | J1
-✅ | 🟢 | Liens avec le web service | 2 | J1
-✅ | 🟡 | Utilisation d'un *Logger* | 1 | J1
-❌ | 🟡 | Déploiement | 4 | J2
-❌ | 🔴 | Unit of Work / Repository + extras (héritage, accès concurrents...) | 8 | J2
-❌ | 🟢 | Utilisation dans le projet | 2 | J2
-✅ | 🟢 | mon dépôt possède un readme qui apporte quelque chose... | 2 | J2
+[ ] | ☢️ | Le dépôt doit être accessible par l'enseignant | ☢️ | J1
+[ ] | ☢️ | un .gitignore doit exister au premier push | ☢️ | J1
+[ ] | 🎬 | les *projets* et les tests compilent | 1 | J1 & J2
+[ ] | 🎬 | le projet et le tests s'exécutent sans bug (concernant la partie persistance) | 3 | J1 & J2
+[ ] | 🟢 | Transcription du modèle : Modèle vers entités (et inversement) | 2 | J1
+[ ] | 🟢 | Requêtes CRUD simples (sur une table) | 1 | J1
+[ ] | 🟢 | Utilisation de LINQ to Entities | 2 | J1
+[ ] | 🟡 | Injection / indépendance du fournisseur | 1 | J1
+[ ] | 🟡 | Requêtes CRUD sur des données complexes (images par exemple) | 2 | J1
+[ ] | 🟢 | Tests - Appli Console | 1 | J1
+[ ] | 🟢 | Tests - Tests unitaires (avec SQLite in memory) | 2 | J1
+[ ] | 🟢 | Tests - Données stubbées et/ou Moq | 1 | J1
+[ ] | 🟡 | CI : build, tests, Sonar (doc?) | 1 | J1
+[ ] | 🟡 | Utilisation de relations (One-to-One, One-to-Many, Many-to-Many) (+ mapping, TU, Requêtes) | 4 | J1
+[ ] | 🟢 | Liens avec le web service | 2 | J1
+[ ] | 🟡 | Utilisation d'un *Logger* | 1 | J1
+[ ] | 🟡 | Déploiement | 4 | J2
+[ ] | 🔴 | Unit of Work / Repository + extras (héritage, accès concurrents...) | 8 | J2
+[ ] | 🟢 | Utilisation dans le projet | 2 | J2
+[ ] | 🟢 | mon dépôt possède un readme qui apporte quelque chose... | 2 | J2
### API
réalisé | niveau | description | coeff | jalon
--- | --- | --- | --- | ---
-✅ | ☢️ | Le dépôt doit être accessible par l'enseignant | ☢️ | J1
-✅ | ☢️ | un .gitignore doit exister au premier push | ☢️ | J1
-✅ | 🎬 | les *projets* et les tests compilent | 1 | J1 & J2
-✅ | 🎬 | le projet et le tests s'exécutent sans bug (concernant la partie persistance) | 4 | J1 & J2
-✅ | 🟢 | Modèle <-> DTO | 1 | J1
-✅ | 🟢 | Entities <-> DTO | 1 | J1
-✅ | 🟡 | Authentification | 4 | J1
-✅ | 🟢 | Requêtes GET, PUT, POST, DELETE sur des données simples (1 seul type d'objet en retour, propriétés de types natifs) | 2 | J1
-✅ | 🟡 | Pagination & filtrage | 2 | J1
-✅ | 🟢 | Injection de service | 2 | J1
-✅ | 🟡 | Requêtes GET, PUT, POST, DELETE sur des données complexes (plusieurs données complexes en retour) | 4 | J1
-✅ | 🟢 | Tests - Appli Console (consommation des requêtes) | 4 | J1
-✅ | 🟢 | Tests - Tests unitaires (avec Stub et/ou Moq) | 2 | J1
-✅ | 🟡 | CI : build, tests, Sonar, Documentation (en particulier Swagger avec exemples...) | 1 | J1
-✅ | 🟢 | Liens avec la persistance en base de données | 4 | J1
-✅ | 🟡 | Utilisation d'un *Logger* | 1 | J1
-❌ | 🟡 | Déploiement | 4 | J2
+[ ] | ☢️ | Le dépôt doit être accessible par l'enseignant | ☢️ | J1
+[ ] | ☢️ | un .gitignore doit exister au premier push | ☢️ | J1
+[ ] | 🎬 | les *projets* et les tests compilent | 1 | J1 & J2
+[ ] | 🎬 | le projet et le tests s'exécutent sans bug (concernant la partie persistance) | 4 | J1 & J2
+[ ] | 🟢 | Modèle <-> DTO | 1 | J1
+[ ] | 🟢 | Entities <-> DTO | 1 | J1
+[ ] | 🟡 | Authentification | 4 | J1
+[ ] | 🟢 | Requêtes GET, PUT, POST, DELETE sur des données simples (1 seul type d'objet en retour, propriétés de types natifs) | 2 | J1
+[ ] | 🟡 | Pagination & filtrage | 2 | J1
+[ ] | 🟢 | Injection de service | 2 | J1
+[ ] | 🟡 | Requêtes GET, PUT, POST, DELETE sur des données complexes (plusieurs données complexes en retour) | 4 | J1
+[ ] | 🟢 | Tests - Appli Console (consommation des requêtes) | 4 | J1
+[ ] | 🟢 | Tests - Tests unitaires (avec Stub et/ou Moq) | 2 | J1
+[ ] | 🟡 | CI : build, tests, Sonar, Documentation (en particulier Swagger avec exemples...) | 1 | J1
+[ ] | 🟢 | Liens avec la persistance en base de données | 4 | J1
+[ ] | 🟡 | Utilisation d'un *Logger* | 1 | J1
+[ ] | 🟡 | Déploiement | 4 | J2
❌ | 🟡 | Utilisation dans le projet | 4 | J2
-✅ | 🎬 | mon dépôt possède un readme qui apporte quelque chose... | 1 | J2
+✅ | 🎬 | mon dépôt possède un readme qui apporte quelque chose... | 1 | J2
## Fabriqué avec

diff --git a/src/APIMappers/ActivityMapper.cs b/src/APIMappers/ActivityMapper.cs
index 2442a40..21af46d 100644
--- a/src/APIMappers/ActivityMapper.cs
+++ b/src/APIMappers/ActivityMapper.cs
@@ -1,4 +1,6 @@
using Dto;
+using Dto.Tiny;
+using Entities;
using Model;
using Shared;
@@ -7,7 +9,8 @@ namespace APIMappers;
public static class ActivityMapper
{
private static GenericMapper _mapper = new();
-
+ private static GenericMapper _mapperTiny = new ();
+
public static Activity ToModel(this ActivityDto activityDto, User user)
{
Func create = activity => new Activity
@@ -72,6 +75,28 @@ public static class ActivityMapper
return model.ToU(_mapper, create, link);
}
+ public static ActivityEntity ToEntity(this ActivityTinyDto tinyDto)
+ {
+ Func create = dto => new ActivityEntity
+ {
+ Type = dto.Type,
+ Date = DateOnly.FromDateTime(dto.Date),
+ StartTime = TimeOnly.FromDateTime(dto.StartTime),
+ EndTime = TimeOnly.FromDateTime(dto.EndTime),
+ EffortFelt = dto.EffortFelt,
+ Variability = dto.Variability,
+ Variance = dto.Variance,
+ StandardDeviation = dto.StandardDeviation,
+ Average = dto.Average,
+ Maximum = dto.Maximum,
+ Minimum = dto.Minimum,
+ AverageTemperature = dto.AverageTemperature,
+ HasAutoPause = dto.HasAutoPause
+ };
+
+ return tinyDto.ToU(_mapperTiny, create);
+ }
+
public static IEnumerable ToModels(this IEnumerable dtos, User user)
=> dtos.Select(dto => dto.ToModel(user));
diff --git a/src/APIMappers/HeartRateMapper.cs b/src/APIMappers/HeartRateMapper.cs
index c5f81a6..bfcaf9f 100644
--- a/src/APIMappers/HeartRateMapper.cs
+++ b/src/APIMappers/HeartRateMapper.cs
@@ -18,7 +18,6 @@ public static class HeartRateMapper
public static HeartRateDto ToDto(this HeartRate model)//Activity activity
{
- // [TODO] [Dave] fix this should be activity but it boucle indefinitly
var activity = new DateTime();
Func create = heartRate =>
new HeartRateDto
diff --git a/src/DbContextLib/HeartTrackContext.cs b/src/DbContextLib/HeartTrackContext.cs
index 3e94ae9..8001980 100644
--- a/src/DbContextLib/HeartTrackContext.cs
+++ b/src/DbContextLib/HeartTrackContext.cs
@@ -7,6 +7,8 @@
//-----------------------------------------------------------------------
using Entities;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace DbContextLib
@@ -14,7 +16,7 @@ namespace DbContextLib
///
/// Represents the database context for the FitnessApp.
///
- public class HeartTrackContext : DbContext
+ public class HeartTrackContext : IdentityDbContext,int>
{
///
/// Gets or sets the set of athletes.
@@ -132,10 +134,10 @@ namespace DbContextLib
//primary key of AthleteEntity
modelBuilder.Entity()
- .HasKey(at => at.IdAthlete);
+ .HasKey(at => at.Id);
//generation mode (at insertion)
modelBuilder.Entity()
- .Property(at => at.IdAthlete)
+ .Property(at => at.Id)
.ValueGeneratedOnAdd();
// add image column type
// modelBuilder.Entity()
@@ -168,16 +170,16 @@ namespace DbContextLib
.ValueGeneratedOnAdd();
modelBuilder.Entity()
- .HasKey(f => new { f.FollowingId, f.FollowerId });
+ .HasKey(f => new { f.FollowerId, f.FollowingId });
modelBuilder.Entity()
.HasOne(fing => fing.Following)
- .WithMany(fings => fings.Followings)
+ .WithMany(fings => fings.Followers)
.HasForeignKey(fing => fing.FollowingId);
modelBuilder.Entity()
.HasOne(fer => fer.Follower)
- .WithMany(fers => fers.Followers)
+ .WithMany(fers => fers.Followings)
.HasForeignKey(fing => fing.FollowerId);
// !
@@ -233,16 +235,24 @@ namespace DbContextLib
.HasMany(ds => ds.Athletes)
.WithOne(at => at.DataSource)
.HasForeignKey(at => at.DataSourceId)
- .IsRequired();
-
- // modelBuilder.Entity()
- // .HasMany(fer => fer.Followers)
- // .WithMany(fing => fing.Followings)
- // .UsingEntity(
- // l => l.HasOne().WithMany().HasForeignKey(fer => fer.FollowerId),
- // r => r.HasOne().WithMany().HasForeignKey(fing => fing.FollowingId),
- // j => j.Property(f => f.StartDate).HasDefaultValueSql("CURRENT_TIMESTAMP")
- // );
+ .IsRequired(false);
+ List> roles =
+ [
+ new()
+ {
+ Id = 1,
+ Name = "Athlete",
+ NormalizedName = "ATHLETE"
+ },
+
+ new()
+ {
+ Id = 2,
+ Name = "Coach",
+ NormalizedName = "COACH"
+ }
+ ];
+ modelBuilder.Entity>().HasData(roles);
}
}
}
\ No newline at end of file
diff --git a/src/DbContextLib/Identity/AuthDbContext.cs b/src/DbContextLib/Identity/AuthDbContext.cs
deleted file mode 100644
index 89924a7..0000000
--- a/src/DbContextLib/Identity/AuthDbContext.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Entities;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore;
-
-namespace DbContextLib.Identity;
-
-public class AuthDbContext: IdentityDbContext
-{
-
- public AuthDbContext(DbContextOptions options) : base(options) { }
- public AuthDbContext() { }
- /*
- ///
- /// Configures the database options if they are not already configured.
- ///
- /// The options builder instance.
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- {
- if (!optionsBuilder.IsConfigured)
- {
- optionsBuilder.UseSqlite($"Data Source=uca.HeartTrack.db");
- }
- }*/
-}
\ No newline at end of file
diff --git a/src/Dto/AthleteDto.cs b/src/Dto/AthleteDto.cs
index d5c94c0..be49bf5 100644
--- a/src/Dto/AthleteDto.cs
+++ b/src/Dto/AthleteDto.cs
@@ -12,7 +12,7 @@ public class UserDto
[MaxLength(100)]
public required string FirstName { get; set; }
public required string Email { get; set; }
- public required string Sexe { get; set; }
+ public required char Sexe { get; set; }
public float Lenght { get; set; }
public float Weight { get; set; }
public string? Password { get; set; }
diff --git a/src/Dto/Auth/AuthRequest.cs b/src/Dto/Auth/AuthRequest.cs
new file mode 100644
index 0000000..af99599
--- /dev/null
+++ b/src/Dto/Auth/AuthRequest.cs
@@ -0,0 +1,13 @@
+
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+
+namespace Dto.Auth;
+
+public class LoginRequestDto
+{
+ [Required(ErrorMessage = "Username is required")]
+ public string Username { get; set; }
+ [Required(ErrorMessage = "Password is required")]
+ public string Password { get; set; }
+}
diff --git a/src/Dto/Auth/AuthResponseDto.cs b/src/Dto/Auth/AuthResponseDto.cs
new file mode 100644
index 0000000..7cab2a4
--- /dev/null
+++ b/src/Dto/Auth/AuthResponseDto.cs
@@ -0,0 +1,23 @@
+namespace Dto.Auth;
+
+public class AuthResponseDto
+{
+ ///
+ /// Gets or sets the access token issued by the OAuth provider.
+ ///
+ public string AccessToken { get; set; }
+
+ /// Gets or sets the token type.
+ /// Typically the string “bearer”.
+ public string? TokenType { get; set; }
+
+ ///
+ /// Gets or sets a refresh token that applications can use to obtain another access token if tokens can expire.
+ ///
+ public string? RefreshToken { get; set; }
+
+ ///
+ /// Gets or sets the validatity lifetime of the token in seconds.
+ ///
+ public string? ExpiresIn { get; set; }
+}
\ No newline at end of file
diff --git a/src/Dto/Auth/RegisterRequestDto.cs b/src/Dto/Auth/RegisterRequestDto.cs
new file mode 100644
index 0000000..c6af1ab
--- /dev/null
+++ b/src/Dto/Auth/RegisterRequestDto.cs
@@ -0,0 +1,33 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Dto.Auth;
+
+public class RegisterRequestDto
+{
+
+ [MaxLength(100)]
+ [Required(ErrorMessage = "Username is required")]
+ public string Username { get; set; }
+ [MaxLength(150)]
+ [Required(ErrorMessage = "LastName is required")]
+ public string LastName { get; set; }
+ [MaxLength(100)]
+ [Required(ErrorMessage = "FirstName is required")]
+ public string FirstName { get; set; }
+ [Required(ErrorMessage = "Email is required")]
+ [EmailAddress]
+ public string Email { get; set; }
+ [Required(ErrorMessage = "Sexe is required")]
+ public char Sexe { get; set; }
+ [Required(ErrorMessage = "Size is required")]
+ public float Size { get; set; }
+ [Required(ErrorMessage = "Weight is required")]
+ public float Weight { get; set; }
+ [Required(ErrorMessage = "Password is required")]
+ public string Password { get; set; }
+ [Required(ErrorMessage = "DateOfBirth is required")]
+ public DateTime DateOfBirth { get; set; }
+ public string ProfilePicture { get; set; } = "https://davidalmeida.site/assets/me_avatar.f77af006.png";
+ [Required(ErrorMessage = "Role is required")]
+ public bool IsCoach { get; set; }
+}
\ No newline at end of file
diff --git a/src/Dto/DataSourceDto.cs b/src/Dto/DataSourceDto.cs
index 74705a1..21eb46b 100644
--- a/src/Dto/DataSourceDto.cs
+++ b/src/Dto/DataSourceDto.cs
@@ -12,11 +12,9 @@ public class DataSourceDto
public float Precision { get; set; }
- // [TODO] [Dave] Add a property to store the athletes and the activities so maybe adapt to have a tiny DTO
[JsonIgnore]
public IEnumerable? Athletes { get; set; }
- // [TODO] [Dave] Add a property to store the athletes and the activities so maybe adapt to have a tiny DTO
[JsonIgnore]
public IEnumerable? Activities { get; set; }
}
\ No newline at end of file
diff --git a/src/Dto/NewActivityDto.cs b/src/Dto/NewActivityDto.cs
new file mode 100644
index 0000000..e3bd0f1
--- /dev/null
+++ b/src/Dto/NewActivityDto.cs
@@ -0,0 +1,12 @@
+using Dto.Tiny;
+
+namespace Dto;
+
+public class NewActivityDto
+{
+ public ActivityTinyDto Activity { get; set; }
+ public HeartRateTinyDto[]? HeartRates { get; set; }
+ public int? DataSourceId { get; set; }
+ public int AthleteId { get; set; }
+
+}
\ No newline at end of file
diff --git a/src/Dto/ResponseActivityDto.cs b/src/Dto/ResponseActivityDto.cs
new file mode 100644
index 0000000..dd92956
--- /dev/null
+++ b/src/Dto/ResponseActivityDto.cs
@@ -0,0 +1,25 @@
+using Dto.Tiny;
+
+namespace Dto;
+
+public class ResponseActivityDto
+{
+ public int Id { get; set; }
+ public string Type { get; set; } = "";
+ public DateTime Date { 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; }
+ public HeartRateTinyDto[]? HeartRates { get; set; }
+ public DataSourceTinyDto? DataSource { get; set; }
+ public UserTinyDto? Athlete { get; set; }
+
+}
\ No newline at end of file
diff --git a/src/Dto/ResponseDataSourceDto.cs b/src/Dto/ResponseDataSourceDto.cs
new file mode 100644
index 0000000..ae260f6
--- /dev/null
+++ b/src/Dto/ResponseDataSourceDto.cs
@@ -0,0 +1,18 @@
+using Dto.Tiny;
+
+namespace Dto;
+
+public class ResponseDataSourceDto
+{
+ public int Id { get; set; }
+
+ public string Type { get; set; } = "Unknown";
+
+ public string Model { get; set; }
+
+ public float Precision { get; set; }
+
+ public ActivityTinyDto[]? Activities { get; set; }
+
+ public UserTinyDto[]? Users { get; set; }
+}
\ No newline at end of file
diff --git a/src/Dto/ResponseUserDto.cs b/src/Dto/ResponseUserDto.cs
new file mode 100644
index 0000000..59a9117
--- /dev/null
+++ b/src/Dto/ResponseUserDto.cs
@@ -0,0 +1,28 @@
+using System.ComponentModel.DataAnnotations;
+using Dto.Tiny;
+
+namespace Dto;
+
+public class ResponseUserDto
+{
+ public int Id { get; set; }
+ [MaxLength(100)]
+ public required string Username { get; set; }
+ [MaxLength(150)]
+ public required string LastName { get; set; }
+ [MaxLength(100)]
+ public required string FirstName { get; set; }
+ public required string Email { get; set; }
+ public required char Sexe { get; set; }
+ public float Lenght { get; set; }
+ public float Weight { get; set; }
+ public string? Password { get; set; }
+ public DateTime DateOfBirth { get; set; }
+ public string ProfilePicture { get; set; } = "https://davidalmeida.site/assets/me_avatar.f77af006.png";
+ public bool IsCoach { get; set; }
+ public LargeImageDto? Image { get; set; }
+ public ActivityTinyDto[] Activities { get; set; }
+ public DataSourceTinyDto? DataSource { get; set; }
+ public FriendshipDto?[] Followers { get; set; }
+ public FriendshipDto?[] Followings { get; set; }
+}
\ No newline at end of file
diff --git a/src/Dto/Tiny/ActivityTinyDto.cs b/src/Dto/Tiny/ActivityTinyDto.cs
new file mode 100644
index 0000000..2865be0
--- /dev/null
+++ b/src/Dto/Tiny/ActivityTinyDto.cs
@@ -0,0 +1,19 @@
+namespace Dto.Tiny;
+
+public class ActivityTinyDto
+{
+ public int? Id { get; set; }
+ public string Type { get; set; } = "";
+ public DateTime Date { 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; }
+}
\ No newline at end of file
diff --git a/src/Dto/Tiny/DataSourceTinyDto.cs b/src/Dto/Tiny/DataSourceTinyDto.cs
new file mode 100644
index 0000000..0cbf471
--- /dev/null
+++ b/src/Dto/Tiny/DataSourceTinyDto.cs
@@ -0,0 +1,12 @@
+namespace Dto.Tiny;
+
+public class DataSourceTinyDto
+{
+ public int Id { get; set; }
+
+ public string Type { get; set; } = "Unknown";
+
+ public string Model { get; set; }
+
+ public float Precision { get; set; }
+}
\ No newline at end of file
diff --git a/src/Dto/Tiny/FriendshipDto.cs b/src/Dto/Tiny/FriendshipDto.cs
new file mode 100644
index 0000000..76deabd
--- /dev/null
+++ b/src/Dto/Tiny/FriendshipDto.cs
@@ -0,0 +1,7 @@
+namespace Dto.Tiny;
+
+public class FriendshipDto
+{
+ public int FollowedId { get; set; }
+ public int FollowerId { get; set; }
+}
\ No newline at end of file
diff --git a/src/Dto/Tiny/HeartRateTinyDto.cs b/src/Dto/Tiny/HeartRateTinyDto.cs
new file mode 100644
index 0000000..a80472b
--- /dev/null
+++ b/src/Dto/Tiny/HeartRateTinyDto.cs
@@ -0,0 +1,16 @@
+namespace Dto.Tiny;
+
+public class HeartRateTinyDto
+{
+ public int Id { get; set; }
+ public DateTime Timestamp { get; set; }
+ public double? Latitude { get; set; }
+ public double? Longitude { get; set; }
+ public double? Altitude { get; set; }
+ public int HeartRate { get; set; }
+ public int? Cadence { get; set; }
+ public double? Distance { get; set; }
+ public double? Speed { get; set; }
+ public int? Power { get; set; }
+ public double? Temperature { get; set; }
+}
\ No newline at end of file
diff --git a/src/Dto/Tiny/UserTinyDto.cs b/src/Dto/Tiny/UserTinyDto.cs
new file mode 100644
index 0000000..789efb8
--- /dev/null
+++ b/src/Dto/Tiny/UserTinyDto.cs
@@ -0,0 +1,22 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Dto.Tiny;
+
+public class UserTinyDto
+{
+ public int Id { get; set; }
+ [MaxLength(100)]
+ public required string Username { get; set; }
+ [MaxLength(150)]
+ public required string LastName { get; set; }
+ [MaxLength(100)]
+ public required string FirstName { get; set; }
+ public required string Email { get; set; }
+ public required char Sexe { get; set; }
+ public float Length { get; set; }
+ public float Weight { get; set; }
+ public string? Password { get; set; }
+ public DateTime DateOfBirth { get; set; }
+ public string ProfilePicture { get; set; } = "https://davidalmeida.site/assets/me_avatar.f77af006.png";
+ public bool IsCoach { get; set; }
+}
\ No newline at end of file
diff --git a/src/EFMappers/AthleteMappeur.cs b/src/EFMappers/AthleteMappeur.cs
index ef779f1..136c457 100644
--- a/src/EFMappers/AthleteMappeur.cs
+++ b/src/EFMappers/AthleteMappeur.cs
@@ -9,18 +9,23 @@ public static class UserMappeur
{
private static GenericMapper _mapper = new ();
+ public static void Reset()
+ {
+ _mapper.Reset();
+ }
+
public static User ToModel(this AthleteEntity entity)
{
Func create = athleteEntity => new User
{
- Id = athleteEntity.IdAthlete,
+ Id = athleteEntity.Id,
FirstName = athleteEntity.FirstName,
LastName = athleteEntity.LastName,
Email = athleteEntity.Email,
- MotDePasse = athleteEntity.Password,
+ MotDePasse = athleteEntity.PasswordHash,
DateOfBirth = athleteEntity.DateOfBirth.ToDateTime(TimeOnly.MinValue),
Sexe = athleteEntity.Sexe,
- Username = athleteEntity.Username,
+ Username = athleteEntity.UserName,
Weight = athleteEntity.Weight,
Lenght = (float)athleteEntity.Length,
ProfilePicture = athleteEntity.ProfilPicture,
@@ -42,13 +47,13 @@ public static class UserMappeur
{
Func create = user => new AthleteEntity
{
- IdAthlete = user.Id,
- Username = user.Username,
+ Id = user.Id,
+ UserName = user.Username,
Sexe = user.Sexe,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
- Password = user.MotDePasse,
+ PasswordHash = user.MotDePasse,
DateOfBirth = DateOnly.FromDateTime(user.DateOfBirth),
IsCoach = user.Role is Coach,
Weight = user.Weight,
@@ -62,17 +67,6 @@ public static class UserMappeur
entity.Activities = user.Activities.ToEntities().ToList();
entity.IsCoach = user.Role is Coach;
entity.Image = user.Image.ToEntity();
- /*if (user.Role is Coach)
- entity.TrainingsCoach = user.Traning.ToEntities().ToList();
- else
- entity.TrainingsAthlete = user.Traning.ToEntities().ToList();
- */
- // entity.NotificationsReceived = user.Notifications.ToEntities().ToList();
-
- // entity.DataSource = user.DataSources.ToEntities().ToList();
-
- // [TODO] [DAVE] : Add the link to the friendship
-
};
return model.ToU(_mapper, create, link);
diff --git a/src/Entities/ActivityEntity.cs b/src/Entities/ActivityEntity.cs
index d1af116..9776074 100644
--- a/src/Entities/ActivityEntity.cs
+++ b/src/Entities/ActivityEntity.cs
@@ -105,6 +105,6 @@ namespace Entities
public int AthleteId { get; set; }
- public AthleteEntity Athlete { get; set; } = null!;
+ public AthleteEntity Athlete { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Entities/AthleteEntity.cs b/src/Entities/AthleteEntity.cs
index 199d635..a661ea8 100644
--- a/src/Entities/AthleteEntity.cs
+++ b/src/Entities/AthleteEntity.cs
@@ -8,56 +8,58 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using Microsoft.AspNetCore.Identity;
namespace Entities
{
+ //dentityRole,int
///
/// Represents an athlete entity in the database.
///
[Table("Athlete")]
- public class AthleteEntity
+ public class AthleteEntity : IdentityUser
{
///
/// Gets or sets the unique identifier of the athlete.
///
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public int IdAthlete { get; set; }
+ public override int Id { get; set; }
///
/// Gets or sets the username of the athlete.
///
[MaxLength(100)]
[Required(ErrorMessage = "Athlete Username is ")]
- public required string Username { get; set; }
+ public override string UserName { get; set; }
///
/// Gets or sets the last name of the athlete.
///
[MaxLength(100)]
[Required(ErrorMessage = "Athlete Last Name is ")]
- public required string LastName { get; set; }
+ public string LastName { get; set; }
///
/// Gets or sets the first name of the athlete.
///
[MaxLength(150)]
[Required(ErrorMessage = "Athlete First Name is ")]
- public required string FirstName { get; set; }
+ public string FirstName { get; set; }
///
/// Gets or sets the email of the athlete.
///
[MaxLength(100)]
[Required(ErrorMessage = "Athlete Email is ")]
- public required string Email { get; set; }
+ public override string Email { get; set; }
///
/// Gets or sets the gender of the athlete.
///
[MaxLength(1)]
[Required(ErrorMessage = "Athlete Sexe is ")]
- public required string Sexe { get; set; }
+ public char Sexe { get; set; }
///
/// Gets or sets the height of the athlete.
@@ -68,12 +70,12 @@ namespace Entities
/// Gets or sets the weight of the athlete.
///
public float Weight { get; set; }
-
+/*
///
/// Gets or sets the password of the athlete.
///
- [Required(ErrorMessage = "Athlete Password is ")]
- public required string Password { get; set; }
+ [Required(ErrorMessage = "Athlete PasswordHash is ")]
+ public string PasswordHash { get; set; }*/
///
/// Gets or sets the date of birth of the athlete.
@@ -87,7 +89,6 @@ namespace Entities
///
public bool IsCoach { get; set; }
- // [TODO] [DAVE] Check Image
public string? ProfilPicture { get; set; }
public LargeImageEntity? Image { get; set; }
diff --git a/src/Entities/Entities.csproj b/src/Entities/Entities.csproj
index b007a4d..86792f8 100644
--- a/src/Entities/Entities.csproj
+++ b/src/Entities/Entities.csproj
@@ -5,4 +5,10 @@
enable
enable
+
+
+
+
+
+
diff --git a/src/Entities2Dto/ActivityMapper.cs b/src/Entities2Dto/ActivityMapper.cs
new file mode 100644
index 0000000..43243a1
--- /dev/null
+++ b/src/Entities2Dto/ActivityMapper.cs
@@ -0,0 +1,101 @@
+using Dto;
+using Dto.Tiny;
+using Entities;
+using Shared;
+
+namespace Entities2Dto;
+
+public static class ActivityMapper
+{
+ private static GenericMapper _mapper = new ();
+
+ private static GenericMapper _mapperFull = new ();
+
+ public static void Reset()
+ {
+ _mapper.Reset();
+ _mapperFull.Reset();
+ }
+
+ public static ActivityTinyDto ToDto(this ActivityEntity entity)
+ {
+ Func create = activityEntity => new ActivityTinyDto
+ {
+ Id = activityEntity.IdActivity,
+ Type = activityEntity.Type,
+ Date = activityEntity.Date.ToDateTime(TimeOnly.MinValue),
+ StartTime = activityEntity.Date.ToDateTime(activityEntity.StartTime),
+ EndTime = activityEntity.Date.ToDateTime(activityEntity.EndTime),
+ EffortFelt = activityEntity.EffortFelt,
+ Variability = activityEntity.Variability,
+ Variance = activityEntity.Variance,
+ StandardDeviation = activityEntity.StandardDeviation,
+ Average = activityEntity.Average,
+ Maximum = activityEntity.Maximum,
+ Minimum = activityEntity.Minimum,
+ AverageTemperature = activityEntity.AverageTemperature,
+ HasAutoPause = activityEntity.HasAutoPause
+ };
+ return entity.ToT(_mapper, create, null, false);
+ }
+
+ public static ActivityEntity ToEntity(this ActivityTinyDto dto)
+ {
+ Func create = activity => new ActivityEntity
+ {
+ Type = activity.Type,
+ Date = DateOnly.FromDateTime(activity.Date),
+ StartTime = TimeOnly.FromDateTime(activity.StartTime),
+ EndTime = TimeOnly.FromDateTime(activity.EndTime),
+ EffortFelt = activity.EffortFelt,
+ Variability = activity.Variability,
+ Variance = activity.Variance,
+ StandardDeviation = activity.StandardDeviation,
+ Average = activity.Average,
+ Maximum = activity.Maximum,
+ Minimum = activity.Minimum,
+ AverageTemperature = activity.AverageTemperature,
+ HasAutoPause = activity.HasAutoPause
+ };
+ return dto.ToU(_mapper, create);
+ }
+
+ public static ResponseActivityDto ToResponseDto(this ActivityEntity entity)
+ {
+ Func create = activityEntity => new ResponseActivityDto
+ {
+ Id = activityEntity.IdActivity,
+ Type = activityEntity.Type,
+ Date = activityEntity.Date.ToDateTime(TimeOnly.MinValue),
+ StartTime = activityEntity.Date.ToDateTime(activityEntity.StartTime),
+ EndTime = activityEntity.Date.ToDateTime(activityEntity.EndTime),
+ EffortFelt = activityEntity.EffortFelt,
+ Variability = activityEntity.Variability,
+ Variance = activityEntity.Variance,
+ StandardDeviation = activityEntity.StandardDeviation,
+ Average = activityEntity.Average,
+ Maximum = activityEntity.Maximum,
+ Minimum = activityEntity.Minimum,
+ AverageTemperature = activityEntity.AverageTemperature,
+ HasAutoPause = activityEntity.HasAutoPause
+ };
+
+ Action linker = (activityEntity, activity) =>
+ {
+ if (activityEntity.HeartRates != null) activity.HeartRates = activityEntity.HeartRates.ToTinyDtos().ToArray();
+ activity.DataSource = activityEntity.DataSource != null ? activityEntity.DataSource.ToTinyDto() : null;
+ activity.Athlete = activityEntity.Athlete.ToTinyDto();
+ };
+
+ return entity.ToT(_mapperFull, create, linker, false);
+ }
+
+ public static IEnumerable ToTinyDtos(this IEnumerable entities)
+ => entities.Select(a => a.ToDto());
+
+ public static IEnumerable ToEntities(this IEnumerable dtos)
+ => dtos.Select(a => a.ToEntity());
+
+
+
+}
\ No newline at end of file
diff --git a/src/Entities2Dto/DataSourceMapper.cs b/src/Entities2Dto/DataSourceMapper.cs
new file mode 100644
index 0000000..2aa5a50
--- /dev/null
+++ b/src/Entities2Dto/DataSourceMapper.cs
@@ -0,0 +1,70 @@
+using Dto;
+using Dto.Tiny;
+using Entities;
+using Shared;
+
+namespace Entities2Dto;
+
+public static class DataSourceMapper
+{
+ private static GenericMapper _mapper = new();
+
+ private static GenericMapper _mapperFull = new();
+
+ public static void Reset()
+ {
+ _mapper.Reset();
+ _mapperFull.Reset();
+ }
+
+ public static DataSourceTinyDto ToTinyDto(this DataSourceEntity entity)
+ {
+ Func create = dataSourceEntity => new DataSourceTinyDto
+ {
+ Id = dataSourceEntity.IdSource,
+ Type = dataSourceEntity.Type,
+ Model = dataSourceEntity.Model,
+ Precision = dataSourceEntity.Precision
+ };
+ return entity.ToT(_mapper, create, null,false);
+ }
+
+ public static DataSourceEntity ToEntity(this DataSourceTinyDto dto)
+ {
+ Func create = dataSource => new DataSourceEntity
+ {
+ IdSource = dataSource.Id,
+ Type = dataSource.Type,
+ Model = dataSource.Model,
+ Precision = dataSource.Precision
+ };
+ return dto.ToU(_mapper, create);
+ }
+
+ public static ResponseDataSourceDto ToResponseDto(this DataSourceEntity entity)
+ {
+ Func create = dataSourceEntity => new ResponseDataSourceDto
+ {
+ Id = dataSourceEntity.IdSource,
+ Type = dataSourceEntity.Type,
+ Model = dataSourceEntity.Model,
+ Precision = dataSourceEntity.Precision,
+ };
+
+ Action linker = (dataSourceEntity, dto) =>
+ {
+ dto.Activities = dataSourceEntity.Activities.ToTinyDtos().ToArray();
+ dto.Users = dataSourceEntity.Athletes.ToTinyDtos().ToArray();
+ };
+
+ return entity.ToT(_mapperFull, create, linker, false);
+ }
+
+ public static IEnumerable ToTinyDtos(this IEnumerable entities)
+ => entities.Select(e => e.ToTinyDto());
+
+ public static IEnumerable ToEntities(this IEnumerable dtos)
+ => dtos.Select(d => d.ToEntity());
+
+
+}
\ No newline at end of file
diff --git a/src/Entities2Dto/Entities2Dto.csproj b/src/Entities2Dto/Entities2Dto.csproj
new file mode 100644
index 0000000..e879b8a
--- /dev/null
+++ b/src/Entities2Dto/Entities2Dto.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+ ..\..\..\.nuget\packages\microsoft.extensions.identity.stores\8.0.2\lib\net8.0\Microsoft.Extensions.Identity.Stores.dll
+
+
+
+
diff --git a/src/Entities2Dto/FriendshipMapper.cs b/src/Entities2Dto/FriendshipMapper.cs
new file mode 100644
index 0000000..ccc9707
--- /dev/null
+++ b/src/Entities2Dto/FriendshipMapper.cs
@@ -0,0 +1,32 @@
+using Dto.Tiny;
+using Entities;
+using Shared;
+
+namespace Entities2Dto;
+
+public static class FriendshipMapper
+{
+ private static GenericMapper _mapper = new();
+
+ public static void Reset()
+ {
+ _mapper.Reset();
+ }
+
+ public static FriendshipDto ToTinyDto(this FriendshipEntity entity)
+ {
+ Func create = friendshipEntity => new FriendshipDto
+ {
+ FollowedId = friendshipEntity.FollowingId,
+ FollowerId = friendshipEntity.FollowerId,
+ };
+
+ return entity.ToT(_mapper, create, null, false);
+ }
+
+ public static IEnumerable ToTinyDtos(this IEnumerable entities)
+ => entities.Select(e => e.ToTinyDto());
+
+
+
+}
\ No newline at end of file
diff --git a/src/Entities2Dto/HeartRateMapper.cs b/src/Entities2Dto/HeartRateMapper.cs
new file mode 100644
index 0000000..5ef863c
--- /dev/null
+++ b/src/Entities2Dto/HeartRateMapper.cs
@@ -0,0 +1,63 @@
+using Dto.Tiny;
+using Entities;
+using Shared;
+
+namespace Entities2Dto;
+
+public static class HeartRateMapper
+{
+
+ private static GenericMapper _mapper = new();
+
+ public static void Reset()
+ {
+ _mapper.Reset();
+ }
+
+ public static HeartRateTinyDto ToTinyDto(this HeartRateEntity entity)
+ {
+ var activityTmp = new DateTime();
+ Func create = heartRateEntity => new HeartRateTinyDto
+ {
+ Id = heartRateEntity.IdHeartRate,
+ HeartRate = heartRateEntity.Bpm,
+ Timestamp = activityTmp,
+ Latitude = heartRateEntity.Latitude,
+ Longitude = heartRateEntity.Longitude,
+ Altitude = heartRateEntity.Altitude,
+ Cadence = heartRateEntity.Cadence,
+ Distance = heartRateEntity.Distance,
+ Speed = heartRateEntity.Speed,
+ Power = heartRateEntity.Power,
+ Temperature = heartRateEntity.Temperature
+ };
+
+ return entity.ToT(_mapper, create, null, false);
+ }
+
+ public static HeartRateEntity ToEntity(this HeartRateTinyDto dto)
+ {
+ Func create = heartRate => new HeartRateEntity
+ {
+ IdHeartRate = heartRate.Id,
+ Bpm = heartRate.HeartRate,
+ Time = TimeOnly.FromDateTime(heartRate.Timestamp),
+ Latitude = heartRate.Latitude,
+ Longitude = heartRate.Longitude,
+ Altitude = heartRate.Altitude,
+ Cadence = heartRate.Cadence,
+ Distance = heartRate.Distance,
+ Speed = heartRate.Speed,
+ Power = heartRate.Power,
+ Temperature = heartRate.Temperature
+ };
+ return dto.ToU(_mapper, create);
+ }
+
+ public static IEnumerable ToTinyDtos(this IEnumerable entities)
+ => entities.Select(e => e.ToTinyDto());
+
+ public static IEnumerable ToEntities(this IEnumerable dtos)
+ => dtos.Select(d => d.ToEntity());
+
+}
\ No newline at end of file
diff --git a/src/Entities2Dto/LargeImageMapper.cs b/src/Entities2Dto/LargeImageMapper.cs
new file mode 100644
index 0000000..8a225d5
--- /dev/null
+++ b/src/Entities2Dto/LargeImageMapper.cs
@@ -0,0 +1,32 @@
+using Dto;
+using Entities;
+using Shared;
+
+namespace Entities2Dto;
+
+public static class LargeImageMapper
+{
+ private static GenericMapper _mapper = new();
+
+ public static void Reset()
+ {
+ _mapper.Reset();
+ }
+
+ public static LargeImageDto ToDto(this LargeImageEntity entity)
+ {
+ Func create = largeImageEntity => new() { Base64 = largeImageEntity.Base64 };
+
+ return entity.ToT(_mapper, create, null, false);
+ }
+
+ public static LargeImageEntity ToEntity(this LargeImageDto dto)
+ {
+ Func create = largeImage => new LargeImageEntity
+ {
+ Base64 = largeImage.Base64
+ };
+
+ return dto.ToU(_mapper, create);
+ }
+}
\ No newline at end of file
diff --git a/src/Entities2Dto/UserMappeur.cs b/src/Entities2Dto/UserMappeur.cs
new file mode 100644
index 0000000..fc5eb53
--- /dev/null
+++ b/src/Entities2Dto/UserMappeur.cs
@@ -0,0 +1,102 @@
+using Dto;
+using Dto.Tiny;
+using Entities;
+using Shared;
+
+namespace Entities2Dto;
+
+public static class UserMappeur
+{
+ private static GenericMapper _mapper = new();
+ private static GenericMapper _mapperFull = new();
+
+
+ public static void Reset()
+ {
+ _mapper.Reset();
+ _mapperFull.Reset();
+ }
+
+ public static UserTinyDto ToTinyDto(this AthleteEntity entity)
+ {
+ Func create = athleteEntity => new UserTinyDto
+ {
+ Id = athleteEntity.Id,
+ FirstName = athleteEntity.FirstName,
+ LastName = athleteEntity.LastName,
+ Email = athleteEntity.Email,
+ Password = athleteEntity.PasswordHash,
+ DateOfBirth = athleteEntity.DateOfBirth.ToDateTime(TimeOnly.MinValue),
+ Sexe = athleteEntity.Sexe,
+ Username = athleteEntity.UserName,
+ Weight = athleteEntity.Weight,
+ Length = (float)athleteEntity.Length,
+ ProfilePicture = athleteEntity.ProfilPicture ?? "",
+ IsCoach = athleteEntity.IsCoach
+ };
+
+ return entity.ToT(_mapper, create, null, false);
+ }
+
+ public static AthleteEntity ToEntity(this UserTinyDto model)
+ {
+ Func create = user => new AthleteEntity
+ {
+ Id = user.Id,
+ UserName = user.Username,
+ Sexe = user.Sexe,
+ FirstName = user.FirstName,
+ LastName = user.LastName,
+ Email = user.Email,
+ PasswordHash = user.Password ?? "",
+ DateOfBirth = DateOnly.FromDateTime(user.DateOfBirth),
+ IsCoach = user.IsCoach,
+ Weight = user.Weight,
+ Length = user.Length,
+ ProfilPicture = user.ProfilePicture
+ };
+
+ return model.ToU(_mapper, create);
+ }
+
+ public static ResponseUserDto ToResponseDto(this AthleteEntity entity)
+ {
+ Func creator = athleteEntity => new ResponseUserDto
+ {
+ Id = athleteEntity.Id,
+ FirstName = athleteEntity.FirstName,
+ LastName = athleteEntity.LastName,
+ Email = athleteEntity.Email,
+ Password = athleteEntity.PasswordHash,
+ DateOfBirth = athleteEntity.DateOfBirth.ToDateTime(TimeOnly.MinValue),
+ Sexe = athleteEntity.Sexe,
+ Username = athleteEntity.UserName,
+ Weight = athleteEntity.Weight,
+ Lenght = (float)athleteEntity.Length,
+ ProfilePicture = athleteEntity.ProfilPicture ?? "",
+ IsCoach = athleteEntity.IsCoach,
+ };
+
+ Action linker = (athleteEntity, userDto) =>
+ {
+ userDto.Activities = athleteEntity.Activities.ToTinyDtos().ToArray();
+ userDto.Image = athleteEntity.Image?.ToDto();
+ userDto.DataSource = athleteEntity.DataSource?.ToTinyDto();
+ userDto.Followers = athleteEntity.Followers.ToTinyDtos().ToArray();
+ userDto.Followings = athleteEntity.Followings.ToTinyDtos().ToArray();
+ };
+
+ return entity.ToT(_mapperFull, creator, linker, false);
+ }
+
+ public static IEnumerable ToTinyDtos(this IEnumerable entities)
+ => entities.Select(e => e.ToTinyDto());
+
+ public static IEnumerable ToEntities(this IEnumerable models)
+ => models.Select(m => m.ToEntity());
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/src/HeartTrack.sln b/src/HeartTrack.sln
index 8bd8954..31d8733 100644
--- a/src/HeartTrack.sln
+++ b/src/HeartTrack.sln
@@ -23,8 +23,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csp
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestsAPI", "TestsAPI", "{30FC2BE9-7397-445A-84AD-043CE70F4281}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientTests", "Tests\TestsAPI\ClientTests\ClientTests.csproj", "{9E4D3AC5-E6CA-4753-BD96-BF5EE793931A}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "Model\Model.csproj", "{30AB7FAA-6072-40B6-A15E-9188B59144F9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestApi", "Tests\TestsAPI\UnitTestApi\UnitTestApi.csproj", "{E515C8B6-6282-4D8B-8523-7B3A13E4AF58}"
@@ -43,6 +41,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "APIMappers", "APIMappers\AP
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestsModel", "Tests\UnitTestsModel\UnitTestsModel.csproj", "{508D380F-145C-437E-A7DF-7A17C526B2F3}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities2Dto", "Entities2Dto\Entities2Dto.csproj", "{1B15D383-1DFA-47E8-86EC-AC631B15FBEB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RepositoriesUnitTest", "Tests\RepositoriesUnitTest\RepositoriesUnitTest.csproj", "{707B1AC4-F896-4270-BC2F-1A589F48979D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAPIConsoleTests", "Tests\WebAPIConsoleTests\WebAPIConsoleTests.csproj", "{D0EE112F-3151-4C28-A6EC-B1CEC7883FAE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -81,10 +85,6 @@ Global
{F80C60E1-1E06-46C2-96DE-42B1C7DE65BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F80C60E1-1E06-46C2-96DE-42B1C7DE65BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F80C60E1-1E06-46C2-96DE-42B1C7DE65BC}.Release|Any CPU.Build.0 = Release|Any CPU
- {9E4D3AC5-E6CA-4753-BD96-BF5EE793931A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {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
{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
@@ -121,6 +121,12 @@ Global
{508D380F-145C-437E-A7DF-7A17C526B2F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{508D380F-145C-437E-A7DF-7A17C526B2F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{508D380F-145C-437E-A7DF-7A17C526B2F3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B15D383-1DFA-47E8-86EC-AC631B15FBEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B15D383-1DFA-47E8-86EC-AC631B15FBEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {707B1AC4-F896-4270-BC2F-1A589F48979D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {707B1AC4-F896-4270-BC2F-1A589F48979D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D0EE112F-3151-4C28-A6EC-B1CEC7883FAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D0EE112F-3151-4C28-A6EC-B1CEC7883FAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -129,11 +135,11 @@ Global
{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}
{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}
{508D380F-145C-437E-A7DF-7A17C526B2F3} = {2B227C67-3BEC-4A83-BDA0-F3918FBC0D18}
+ {D0EE112F-3151-4C28-A6EC-B1CEC7883FAE} = {30FC2BE9-7397-445A-84AD-043CE70F4281}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0F3487F4-66CA-4034-AC66-1BC899C9B523}
diff --git a/src/HeartTrackAPI/Controllers/ActivityController.cs b/src/HeartTrackAPI/Controllers/ActivityController.cs
index b6e160b..41d51af 100644
--- a/src/HeartTrackAPI/Controllers/ActivityController.cs
+++ b/src/HeartTrackAPI/Controllers/ActivityController.cs
@@ -1,9 +1,10 @@
using APIMappers;
using Dto;
+using Dto.Tiny;
using HeartTrackAPI.Request;
using HeartTrackAPI.Responce;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
-using Model;
using Shared;
using Model.Manager;
using Model.Repository;
@@ -12,25 +13,29 @@ namespace HeartTrackAPI.Controllers;
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
+[Authorize]
+
public class ActivityController : Controller
{
private readonly IActivityRepository _activityService;
private readonly ILogger _logger;
private readonly IUserRepository _userRepository;
+ private readonly IDataSourceRepository _dataSourceRepository;
public ActivityController(IDataManager dataManager, ILogger logger)
{
_activityService = dataManager.ActivityRepo;
_userRepository = dataManager.UserRepo;
+ _dataSourceRepository = dataManager.DataSourceRepo;
_logger = logger;
}
[HttpGet]
- [ProducesResponseType(typeof(PageResponse), 200)]
+ [ProducesResponseType(typeof(PageResponse), 200)]
[ProducesResponseType(400)]
- [ProducesResponseType(500)]
- public async Task>> GetActivities([FromQuery] PageRequest pageRequest)
+ [ProducesResponseType(500)]
+ public async Task>> GetActivities([FromQuery] PageRequest pageRequest)
{
try
{
@@ -46,7 +51,9 @@ public class ActivityController : Controller
{
return NotFound("No activities found");
}
- var pageResponse = new PageResponse(pageRequest.Index, pageRequest.Count, totalCount, activities.Select(a => a.ToDto()));
+
+ var pageResponse =
+ new PageResponse(pageRequest.Index, pageRequest.Count, totalCount, activities);
return Ok(pageResponse);
}
catch (Exception e)
@@ -57,98 +64,52 @@ public class ActivityController : Controller
}
[HttpPost]
- public async Task PostActivity(ActivityDto activityDto)
+ public async Task PostActivity(NewActivityDto activityDto)
{
- var user = await _userRepository.GetItemById(activityDto.AthleteId);
+ _logger.LogInformation("Executing {Action} with parameters: {Parameters}, {add}", nameof(PostActivity), activityDto.Activity.Average, activityDto.HeartRates[0].Timestamp);
+ var user = await _userRepository.GetUserTinyById(activityDto.AthleteId);
if (user == null)
{
_logger.LogError("Athlete with id {id} not found", activityDto.AthleteId);
return NotFound($"Athlete with id {activityDto.AthleteId} not found");
}
- var tmp = user.DataSources.FirstOrDefault(ds => ds.Id == activityDto.DataSourceId);
- if (tmp == null)
+ if (activityDto.DataSourceId != null)
{
- _logger.LogError("DataSource with id {id} not found", activityDto.DataSourceId);
- return NotFound($"DataSource with id {activityDto.DataSourceId} not found");
+ var dataSource = await _dataSourceRepository.GetItemById(activityDto.DataSourceId.Value);
+
+ if (dataSource == null)
+ {
+ _logger.LogError("DataSource with id {id} not found", activityDto.DataSourceId);
+ return NotFound($"DataSource with id {activityDto.DataSourceId} not found");
+ }
}
- var activity = activityDto.ToModel(user);
- var result = await _activityService.AddActivity(activity);
+ var result = await _activityService.AddActivity(activityDto);
+
if (result == null)
{
return BadRequest();
}
- return CreatedAtAction(nameof(GetActivity), new { id = result.Id }, result.ToDto());
- }
-
- /*
- public async Task PostFitFile( Stream file, string contentType) // [FromForm]
- {
- if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
- {
- ModelState.AddModelError("File",
- $"The request couldn't be processed (Error 1).");
- // Log error
+ _logger.LogInformation("Activity added with id {id}", result.Id);
- return BadRequest(ModelState);
- }
- if (file == null)
- {
- return BadRequest("No file was provided");
- }
- //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(GetActivity), new { id = activity.Id }, activity.ToDto());
- }*/
+ return CreatedAtAction(nameof(GetActivity), new { id = result.Id }, result);
+ }
[HttpGet("{id}")]
- public async Task> GetActivity(int id)
+ public async Task> GetActivity(int id)
{
try
{
_logger.LogInformation("Executing {Action} with parameters: {Parameters}", nameof(GetActivity), id);
- var activity = await _activityService.GetActivityByIdAsync(id);
+ var activity = await _activityService.GetActivityById(id);
if (activity == null)
{
_logger.LogError("Activity with id {id} not found", id);
return NotFound($"Activity with id {id} not found");
}
- return Ok(activity.ToDto());
+ return Ok(activity);
}
catch (Exception e)
{
@@ -158,25 +119,15 @@ public class ActivityController : Controller
}
[HttpPut("{id}")]
- public async Task PutActivity(int id, ActivityDto activityDto)
+ public async Task PutActivity(int id, ActivityTinyDto activityDto)
{
- if (id != activityDto.Id)
- {
- return BadRequest();
- }
- var user = await _userRepository.GetItemById(activityDto.AthleteId);
- if (user == null)
- {
- _logger.LogError("Athlete with id {id} not found", activityDto.AthleteId);
- return NotFound($"Athlete with id {activityDto.AthleteId} not found");
- }
- var activity = activityDto.ToModel(user);
- var result = await _activityService.UpdateActivity(id, activity);
+ var result = await _activityService.UpdateActivity(id, activityDto);
if (result == null)
{
return NotFound();
}
- return NoContent();
+
+ return Ok(result);
}
///
diff --git a/src/HeartTrackAPI/Controllers/AnalysisController.cs b/src/HeartTrackAPI/Controllers/AnalysisController.cs
new file mode 100644
index 0000000..96fe0aa
--- /dev/null
+++ b/src/HeartTrackAPI/Controllers/AnalysisController.cs
@@ -0,0 +1,116 @@
+using Dto;
+using Dto.Tiny;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Model.Manager;
+using Model.Repository;
+using Model.utils;
+
+namespace HeartTrackAPI.Controllers;
+
+[ApiController]
+[ApiVersion("1.0")]
+[Route("api/[controller]")]
+[Authorize]
+public class AnalysisController : Controller
+{
+
+ private readonly IActivityRepository _activityService;
+ private readonly ILogger _logger;
+
+
+ public AnalysisController(IDataManager dataManager, ILogger logger)
+ {
+ _activityService = dataManager.ActivityRepo;
+ _logger = logger;
+ }
+
+
+
+
+ [HttpGet("activity/{activityId}")]
+ public async Task AnalyseByActivityId(int activityId)
+ {
+ var activity = await _activityService.GetActivityById(activityId);
+ if (activity == null)
+ {
+ _logger.LogInformation($"Activity with ID {activityId} not found.");
+ return NotFound($"Activity with ID {activityId} not found.");
+ }
+ // for the moment no need to get the user Entity [Dave]
+ var user = activity.Athlete;
+ if (user == null)
+ {
+ _logger.LogInformation($"User not found for activity ID {activityId}.");
+ return NotFound($"User not found for activity ID {activityId}.");
+ }
+
+ var analysis = ActivityAnalysis.FromActivityData(activity);
+ return Ok(analysis);
+ }
+
+
+}
+
+
+/*
+ /*
+ public class HeartRateZoneResult
+ {
+ public string Zone { get; set; }
+ public TimeSpan TimeSpent { get; set; }
+ }
+
+ private readonly List _heartRateZones = new()
+ {
+ new() { Name = "Repos", MinHeartRate = 0, MaxHeartRate = 60 },
+ new() { Name = "Aérobie légère", MinHeartRate = 61, MaxHeartRate = 90 },
+ new() { Name = "Aérobie", MinHeartRate = 91, MaxHeartRate = 140 },
+ new() { Name = "Anaérobie", MinHeartRate = 141, MaxHeartRate = 180 },
+ new() { Name = "VO2 Max", MinHeartRate = 181, MaxHeartRate = 220 }
+ };
+ [HttpGet("heart-rate/zones/{activityId}")]
+ public IActionResult GetActivityHeartRateZones(int activityId)
+ {
+ var heartRateTinyDtos = _activityService.GetActivityById(activityId).Result?.HeartRates;
+ if (heartRateTinyDtos != null)
+ {
+ var heartRates = heartRateTinyDtos.ToList();
+ var results = _heartRateZones.Select(zone => new HeartRateZoneResult
+ {
+ Zone = zone.Name,
+ TimeSpent = CalculateTimeInZone(zone, heartRates)
+ }).ToList();
+
+ return Ok(results);
+ }
+
+ return NotFound("Not heart rates");
+
+ }
+ private TimeSpan CalculateTimeInZone(HeartRateZone zone, List heartRates)
+ {
+ var secondsInZone =
+ heartRates.Count(hr => hr.HeartRate >= zone.MinHeartRate && hr.HeartRate <= zone.MaxHeartRate);
+ return TimeSpan.FromSeconds(secondsInZone);
+ }* /
+
+ [HttpGet("getOptimizedPath")]
+ public IActionResult GetOptimizedPath(int activityId)
+ {
+ var heartRateData = GetMockHeartRateData();
+
+ var sortedData = heartRateData.OrderBy(x => x.Timestamp).ToList();
+
+ var path = new GeoJsonPath();
+ foreach (var item in sortedData)
+ {
+ if (item.Latitude.HasValue && item.Longitude.HasValue)
+ {
+ path.Coordinates.Add(new GeoJsonCoordinate(item.Longitude.Value, item.Latitude.Value));
+ }
+ }
+
+ return Ok(path.ToGeoJson());
+ }
+*/
\ No newline at end of file
diff --git a/src/HeartTrackAPI/Controllers/AuthController.cs b/src/HeartTrackAPI/Controllers/AuthController.cs
new file mode 100644
index 0000000..27a1e85
--- /dev/null
+++ b/src/HeartTrackAPI/Controllers/AuthController.cs
@@ -0,0 +1,154 @@
+using System.Globalization;
+using Dto.Auth;
+using Dto.Tiny;
+using Entities;
+using HeartTrackAPI.Request;
+using HeartTrackAPI.Services;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace HeartTrackAPI.Controllers;
+[ApiController]
+[ApiVersion("1.0")]
+[Route("api/v{version:apiVersion}/[controller]")]
+public class AuthController : Controller
+{
+
+ private readonly UserManager _userManager;
+ private readonly ITokenService _tokenService;
+ private readonly SignInManager _signinManager;
+
+ public AuthController(UserManager userManager,ITokenService tokenService, SignInManager signinManager)
+ {
+ _userManager = userManager;
+ _tokenService = tokenService;
+ _signinManager = signinManager;
+ }
+ [HttpPost("login")]
+ public async Task Login(LoginRequestDto loginDto)
+ {
+ if (!ModelState.IsValid)
+ return BadRequest(ModelState);
+
+ var user = await _userManager.Users.FirstOrDefaultAsync(x => x.UserName == loginDto.Username.ToLower());
+
+ if (user == null) return Unauthorized("Invalid username!");
+
+ var result = await _signinManager.CheckPasswordSignInAsync(user, loginDto.Password, false);
+
+ if (!result.Succeeded) return Unauthorized("Username not found and/or password incorrect");
+
+ return Ok(new AuthResponseDto
+ {
+ AccessToken = _tokenService.CreateToken(user),
+ ExpiresIn = DateTime.Now.AddDays(7).ToString(CultureInfo.InvariantCulture),
+ TokenType = "Bearer"
+ }
+ );
+ }
+
+ [HttpPost("register")]
+ public async Task Register([FromBody] RegisterRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ return BadRequest(ModelState);
+ // just for testing
+ // the good way is to use the repository and give him the userManager
+ var user = new AthleteEntity
+ {
+ Email = request.Email,
+ UserName = request.Username,
+ LastName = request.LastName,
+ FirstName = request.FirstName,
+ Sexe = request.Sexe,
+ Length = request.Size,
+ Weight = request.Weight,
+ DateOfBirth = DateOnly.FromDateTime(request.DateOfBirth),
+ IsCoach = request.IsCoach
+ };
+ var createdUser = _userManager.CreateAsync(user, request.Password).Result;
+ if (createdUser.Succeeded)
+ {
+ var roleResult = await _userManager.AddToRoleAsync(user, request.IsCoach ? "Coach" : "Athlete");
+ if (roleResult.Succeeded)
+ {
+ return Ok(
+ new AuthResponseDto
+ {
+ AccessToken = _tokenService.CreateToken(user),
+ ExpiresIn = DateTime.Now.AddDays(7).ToString(),
+ TokenType = "Bearer"
+ }
+ );
+ }
+ {
+ return StatusCode(500, roleResult.Errors);
+ }
+ }
+ {
+ return StatusCode(500, createdUser.Errors);
+ }
+ }
+ catch (Exception e)
+ {
+ return StatusCode(500, e.Message);
+ }
+
+
+ /* var user = _userRepository.GetByEmail(request.Email);
+ if (user != null)
+ {
+ return BadRequest("User already exists");
+ }
+ var newUser = new User
+ {
+ Email = request.Email,
+ PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.PasswordHash),
+ FirstName = request.FirstName,
+ LastName = request.LastName
+ };
+ _userRepository.Add(newUser);
+ return Ok();*/
+ }
+ /*
+ [HttpPost("refresh")]
+ public IActionResult Refresh([FromBody] RefreshRequest request)
+ {
+ var user = _userRepository.GetByEmail(request.Email);
+ if (user == null)
+ {
+ return Unauthorized();
+ }
+ if (!BCrypt.Net.BCrypt.Verify(request.PasswordHash, user.PasswordHash))
+ {
+ return Unauthorized();
+ }
+ var token = _jwtService.GenerateToken(user);
+ return Ok(new { token });
+ }
+ */
+ [HttpPost("logout")]
+ public IActionResult Logout()
+ {
+ return Ok();
+ }
+ /*
+
+ [HttpPost("forgot-password")]
+ public IActionResult ForgotPassword([FromBody] ForgotPasswordRequest request)
+ {
+ var user = _userRepository.GetByEmail(request.Email);
+ if (user == null)
+ {
+ return BadRequest("User not found");
+ }
+ var token = _jwtService.GenerateToken(user);
+ // send email with token
+ return Ok();
+ }*/
+
+
+}
\ No newline at end of file
diff --git a/src/HeartTrackAPI/Controllers/UsersController.cs b/src/HeartTrackAPI/Controllers/UsersController.cs
index cd9f0b6..8f723ac 100644
--- a/src/HeartTrackAPI/Controllers/UsersController.cs
+++ b/src/HeartTrackAPI/Controllers/UsersController.cs
@@ -1,8 +1,11 @@
using System.ComponentModel.DataAnnotations;
using APIMappers;
using Dto;
+using Dto.Tiny;
using HeartTrackAPI.Request;
using HeartTrackAPI.Responce;
+using HeartTrackAPI.Utils;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Model.Manager;
@@ -17,6 +20,7 @@ namespace HeartTrackAPI.Controllers;
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
+[Authorize]
public class UsersController : Controller
{
private readonly ILogger _logger;
@@ -38,10 +42,10 @@ public class UsersController : Controller
/// La demande de pagination est invalide.
/// Erreur interne du serveur.
[HttpGet]
- [ProducesResponseType(typeof(PageResponse), 200)]
+ [ProducesResponseType(typeof(PageResponse), 200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
- public async Task>> Get([FromQuery] PageRequest request)
+ public async Task>> Get([FromQuery] PageRequest request)
{
try
{
@@ -54,8 +58,8 @@ 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(request.Index, request.Count, totalCount, athletes!.Select(a => a.ToDto()));
+ var athletes = await _userService.GetUsersTiny(request.Index, request.Count, Enum.TryParse(request.OrderingPropertyName, out AthleteOrderCriteria result) ? result : AthleteOrderCriteria.None, request.Descending ?? false);
+ var pageResponse = new PageResponse(request.Index, request.Count, totalCount, athletes);
return Ok(pageResponse);
}
catch (Exception e)
@@ -74,21 +78,21 @@ public class UsersController : Controller
/// Aucun utilisateur trouvé pour l'identifiant spécifié.
/// Erreur interne du serveur.
[HttpGet("{id}")]
- [ProducesResponseType(typeof(UserDto), 200)]
+ [ProducesResponseType(typeof(ResponseUserDto), 200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
- public async Task> GetById([Range(0,int.MaxValue)]int id)
+ public async Task> GetById([Range(0,int.MaxValue)]int id)
{
try
{
_logger.LogInformation("Executing {Action} with parameters: {Parameters}", nameof(GetById), id);
- var athlete = await _userService.GetItemById(id);
+ var athlete = await _userService.GetUserById(id);
if (athlete == null)
{
_logger.LogError("Athlete with id {id} not found", id);
return NotFound($"Athlete with id {id} not found");
}
- return Ok(athlete.ToDto());
+ return Ok(athlete);
}
catch (Exception e)
{
@@ -110,7 +114,7 @@ public class UsersController : Controller
{
try
{
- _logger.LogInformation("Executing {Action} with parameters: {Parameters}", nameof(Count), null);
+ _logger.LogInformation("Executing {Action}", nameof(Count));
var nbUsers = await _userService.GetNbItems();
return Ok(nbUsers);
}
@@ -131,27 +135,27 @@ public class UsersController : Controller
/// Utilisateur non trouvé.
/// Erreur interne du serveur.
[HttpPut("{id}")]
- [ProducesResponseType(typeof(UserDto), 200)]
+ [ProducesResponseType(typeof(UserTinyDto), 200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
- public async Task> Update(int id, [FromBody] UserDto user)
+ public async Task> Update(int id, [FromBody] UserTinyDto user)
{
try
{
_logger.LogInformation("Executing {Action} with parameters: {Parameters} for {Id}", nameof(Update), user,id);
- var athlete = await _userService.GetItemById(id);
+ var athlete = await _userService.GetUserTinyById(id);
if (athlete == null)
{
_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());
+ var updatedAthlete = await _userService.UpdateUser(id, user);
if(updatedAthlete == null)
{
_logger.LogError("Error while updating athlete with id {id}", id);
return Problem();
}
- return Ok(updatedAthlete.ToDto());
+ return Ok(updatedAthlete);
}
catch (Exception e)
@@ -178,9 +182,8 @@ public class UsersController : Controller
try
{
_logger.LogInformation("Executing {Action} with parameters: {Parameters} for {Id}", nameof(Delete), null,id);
-
-
- var athlete = await _userService.GetItemById(id);
+
+ var athlete = await _userService.GetUserTinyById(id);
if (athlete == null)
{
_logger.LogError("Athlete with id {id} not found", id);
@@ -211,31 +214,30 @@ public class UsersController : Controller
/// Utilisateur non trouvé.
/// Erreur interne du serveur.
[HttpGet("{id}/friends")]
- [ProducesResponseType(typeof(PageResponse), 200)]
+ [ProducesResponseType(typeof(PageResponse), 200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
- public async Task>> GetFriends(int id, [FromQuery] PageRequest request)
+ public async Task>> 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);
+ var totalCount = await _userService.GetNbFriends(id);
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);
+ var friends = await _userService.GetFriends(id, 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(request.Index, request.Count, totalCount, friends.Select(a => a.ToDto()));
+ var pageResponse = new PageResponse(request.Index, request.Count, totalCount, friends);
return Ok(pageResponse);
}
+ catch(ModelNotFoundException e)
+ {
+ _logger.LogError(e, "Error while adding a friend to an athlete");
+ return BadRequest(e.Message);
+ }
catch (Exception e)
{
_logger.LogError(e, "Error while getting the number of users");
@@ -256,24 +258,13 @@ public class UsersController : Controller
[ProducesResponseType(200)]
[ProducesResponseType(404)]
[ProducesResponseType(500)]
- public async Task AddFriend(int id, int friendId)
+ public async Task AddFollowing(int id, int friendId)
{
try
{
- _logger.LogInformation("Executing {Action} with parameters: {Parameters} for {Id}", nameof(AddFriend), 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 isAdded = await _userService.AddFriend(athlete, friend);
+ _logger.LogInformation("Executing {Action} with parameters: {Parameters} for {Id}", nameof(AddFollowing), friendId,id);
+
+ var isAdded = await _userService.AddFollowing(id, friendId);
if(!isAdded)
{
_logger.LogError("Error while adding friend with id {friendId} to athlete with id {id}", friendId, id);
@@ -281,9 +272,14 @@ public class UsersController : Controller
}
return Ok();
}
+ catch(FriendShipException e)
+ {
+ _logger.LogError(e, "Error while adding a friend to an athlete");
+ return BadRequest(e.Message);
+ }
catch (Exception e)
{
- _logger.LogError(e, "Error while getting the number of users");
+ _logger.LogError(e, "Error while attempting to follow a user");
return Problem();
}
}
@@ -307,19 +303,8 @@ public class UsersController : Controller
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);
+
+ var isRemoved = await _userService.RemoveFollowing(id, friendId);
if(!isRemoved)
{
_logger.LogError("Error while removing friend with id {friendId} to athlete with id {id}", friendId, id);
@@ -327,55 +312,19 @@ public class UsersController : Controller
}
return Ok();
}
- catch (Exception e)
+ catch(FriendShipException e)
{
- _logger.LogError(e, "Error while getting the number of users");
- return Problem();
- }
- }
-
- ///
- /// 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)]
- [ProducesResponseType(500)]
- public async Task>> 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(request.Index, request.Count, totalCount, athletes.Select(a => a.ToDto()));
- return Ok(pageResponse);
+ _logger.LogError(e, "Error while removing a friend to an athlete");
+ return BadRequest(e.Message);
}
catch (Exception e)
{
- _logger.LogError(e, "Error while getting the number of users");
+ _logger.LogError(e, "Error while attempting to unfollow a user");
return Problem();
}
}
-
+
+ /*
///
/// Obtient la liste des activités d'un utilisateur spécifique.
///
@@ -387,7 +336,7 @@ public class UsersController : Controller
/// 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)
+ public async Task>> GetActivitiesByUser(int userId, [FromQuery] PageRequest pageRequest)
{
try
{
@@ -403,7 +352,7 @@ public class UsersController : Controller
{
return NotFound("No activities found");
}
- var pageResponse = new PageResponse(pageRequest.Index, pageRequest.Count, totalCount, activities.Select(a => a.ToDto()));
+ var pageResponse = new PageResponse(pageRequest.Index, pageRequest.Count, totalCount, activities.Select(a => a.ToDto()));
return Ok(pageResponse);
}
catch (Exception e)
@@ -411,26 +360,7 @@ public class UsersController : Controller
_logger.LogError(e, "Error while getting all activities");
return Problem();
}
- }
-
- ///
- /// 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)]
- [ProducesResponseType(500)]
- public async Task Logout(SignInManager signInManager, [FromBody] object? empty)
- {
- if (empty == null) return Unauthorized();
- await signInManager.SignOutAsync();
- return Ok();
- }
+ }*/
+
}
\ No newline at end of file
diff --git a/src/HeartTrackAPI/HeartTrackAPI.csproj b/src/HeartTrackAPI/HeartTrackAPI.csproj
index e815c27..ab6b8d3 100644
--- a/src/HeartTrackAPI/HeartTrackAPI.csproj
+++ b/src/HeartTrackAPI/HeartTrackAPI.csproj
@@ -10,11 +10,13 @@
+
+
diff --git a/src/HeartTrackAPI/Program.cs b/src/HeartTrackAPI/Program.cs
index b0010a6..a0b177e 100644
--- a/src/HeartTrackAPI/Program.cs
+++ b/src/HeartTrackAPI/Program.cs
@@ -1,4 +1,6 @@
+using DbContextLib;
using HeartTrackAPI.Utils;
+using StubbedContextLib;
var builder = WebApplication.CreateBuilder(args);
@@ -16,4 +18,8 @@ var app = builder.Build();
init.Configure(app, app.Environment);
+var context = app.Services.GetService() ?? app.Services.GetService();
+
+context!.Database.EnsureCreated();
+
app.Run();
\ No newline at end of file
diff --git a/src/HeartTrackAPI/Request/PageRequest.cs b/src/HeartTrackAPI/Request/PageRequest.cs
index a4399f4..e7651d4 100644
--- a/src/HeartTrackAPI/Request/PageRequest.cs
+++ b/src/HeartTrackAPI/Request/PageRequest.cs
@@ -7,10 +7,6 @@ public class PageRequest
{
public string? OrderingPropertyName { get; set; } = null;
public bool? Descending { get; set; } = false;
-
-// [Range(0, int.MaxValue, ErrorMessage = "Count must be greater than 0")]
public int Index { get; set; } = 0;
-
-// [Range(0, int.MaxValue, ErrorMessage = "Count must be greater than 0")]
public int Count { get; set; } = 1;
}
diff --git a/src/HeartTrackAPI/Services/TokenService.cs b/src/HeartTrackAPI/Services/TokenService.cs
new file mode 100644
index 0000000..92de2f7
--- /dev/null
+++ b/src/HeartTrackAPI/Services/TokenService.cs
@@ -0,0 +1,49 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using Entities;
+using Microsoft.IdentityModel.Tokens;
+
+namespace HeartTrackAPI.Services;
+public interface ITokenService
+{
+ string CreateToken(AthleteEntity user);
+}
+
+public class TokenService : ITokenService
+{
+ private readonly IConfiguration _config;
+ private readonly SymmetricSecurityKey _key;
+
+ public TokenService(IConfiguration config)
+ {
+ _config = config;
+ _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["JWT:SigningKey"]));
+ }
+ public string CreateToken(AthleteEntity user)
+ {
+ var claims = new List
+ {
+ new (JwtRegisteredClaimNames.Email, user.Email),
+ new (JwtRegisteredClaimNames.NameId, user.Id.ToString()),
+ new (JwtRegisteredClaimNames.GivenName, user.UserName)
+ };
+
+ var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
+
+ var tokenDescriptor = new SecurityTokenDescriptor
+ {
+ Subject = new ClaimsIdentity(claims),
+ Expires = DateTime.Now.AddDays(7),
+ SigningCredentials = creds,
+ Issuer = _config["JWT:Issuer"],
+ Audience = _config["JWT:Audience"]
+ };
+
+ var tokenHandler = new JwtSecurityTokenHandler();
+
+ var token = tokenHandler.CreateToken(tokenDescriptor);
+
+ return tokenHandler.WriteToken(token);
+ }
+}
\ No newline at end of file
diff --git a/src/HeartTrackAPI/Utils/AppBootstrap.cs b/src/HeartTrackAPI/Utils/AppBootstrap.cs
index c629f5e..e15d89f 100644
--- a/src/HeartTrackAPI/Utils/AppBootstrap.cs
+++ b/src/HeartTrackAPI/Utils/AppBootstrap.cs
@@ -1,12 +1,13 @@
using System.Reflection;
using DbContextLib;
-using DbContextLib.Identity;
-using HeartTrackAPI.Utils;
+using Entities;
+using HeartTrackAPI.Services;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Model.Manager;
using Model2Entities;
@@ -23,19 +24,19 @@ public class AppBootstrap(IConfiguration configuration)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
+
services.AddEndpointsApiExplorer();
AddSwagger(services);
AddHeartTrackContextServices(services);
AddModelService(services);
- AddIdentityServices(services);
+ AddIdentityServices(services, configuration);
AddApiVersioning(services);
services.AddHealthChecks();
-
}
private void AddHeartTrackContextServices(IServiceCollection services)
{
- string? connectionString;
+ string? connectionString;
switch (Environment.GetEnvironmentVariable("TYPE"))
{
@@ -49,40 +50,38 @@ public class AppBootstrap(IConfiguration configuration)
connectionString = $"Server={host};port={port};database={database};user={username};password={password}";
Console.WriteLine("========RUNNING USING THE MYSQL SERVER==============");
Console.WriteLine(connectionString);
- Console.WriteLine(connectionString);
- Console.WriteLine("======================");
- services.AddDbContext(options => options.UseInMemoryDatabase("AuthDb"));
+ Console.WriteLine("====================================================");
services.AddDbContext(options =>
- options.UseMySql($"{connectionString}", new MySqlServerVersion(new Version(10, 11, 1)))
- , ServiceLifetime.Singleton);
+ options.UseMySql($"{connectionString}", new MySqlServerVersion(new Version(10, 11, 1)))
+ , ServiceLifetime.Singleton);
break;
default:
Console.WriteLine("====== RUNNING USING THE IN SQLITE DATABASE ======");
connectionString = Configuration.GetConnectionString("HeartTrackAuthConnection");
+ Console.WriteLine(connectionString);
if (!string.IsNullOrWhiteSpace(connectionString))
{
- services.AddDbContext(options => options.UseInMemoryDatabase("AuthDb"));
services.AddDbContext(options =>
options.UseSqlite(connectionString), ServiceLifetime.Singleton);
}
else
{
- services.AddDbContext(options => options.UseInMemoryDatabase("AuthDb"));
services.AddDbContext();
}
+
break;
-
}
}
-
+
private void AddModelService(IServiceCollection services)
{
switch (Environment.GetEnvironmentVariable("TYPE"))
{
case "BDD":
- services.AddSingleton(provider => new DbDataManager(provider.GetRequiredService()));
+ services.AddSingleton(provider =>
+ new DbDataManager(provider.GetRequiredService()));
break;
- case "STUB":
+ case "STUB-MODEL":
services.AddSingleton();
break;
default:
@@ -91,25 +90,80 @@ public class AppBootstrap(IConfiguration configuration)
provider.GetRequiredService().Database.EnsureCreated();
return new DbDataManager(provider.GetRequiredService());
});
+
+ // services.AddSingleton(provider => new DbDataManager(provider.GetRequiredService()));
break;
}
+ // Auth
- //services.AddTransient();
+ services.AddScoped();
}
- private void AddIdentityServices(IServiceCollection services)
+ private void AddIdentityServices(IServiceCollection services, IConfiguration config)
{
+ /*services.AddAuthorization();
+ services.AddIdentityApiEndpoints()
+ .AddEntityFrameworkStores();*/
+
+ var identityBuilder = services.AddIdentity>(options =>
+ {
+ options.Password.RequireDigit = true;
+ options.Password.RequireLowercase = true;
+ options.Password.RequireUppercase = true;
+ options.Password.RequireNonAlphanumeric = true;
+ options.Password.RequiredLength = 8;
+ });
+
+ if (Environment.GetEnvironmentVariable("TYPE") == "BDD")
+ {
+ identityBuilder.AddEntityFrameworkStores();
+ }
+ else
+ {
+ identityBuilder.AddEntityFrameworkStores();
+ }
+
+ services.AddAuthentication(options =>
+ {
+ options.DefaultAuthenticateScheme =
+ options.DefaultChallengeScheme =
+ options.DefaultForbidScheme =
+ options.DefaultScheme =
+ options.DefaultSignInScheme =
+ options.DefaultSignOutScheme = JwtBearerDefaults.AuthenticationScheme;
+ }).AddJwtBearer(options =>
+ {
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = true,
+ ValidIssuer =config["JWT:Issuer"],
+ ValidateAudience = true,
+ ValidAudience = config["JWT:Audience"],
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(
+ System.Text.Encoding.UTF8.GetBytes(config["JWT:SigningKey"])
+ )
+ };
+ });
+
+ /*
+ app.UseCors(x => x
+ .AllowAnyMethod()
+ .AllowAnyHeader()
+ .AllowCredentials()
+ //.WithOrigins("https://localhost:44351))
+ .SetIsOriginAllowed(origin => true));*/
+
// services.AddTransient, EmailSender>();
- services.AddAuthorization();
+ // services.AddAuthorization();
- services.AddIdentityApiEndpoints()
- .AddEntityFrameworkStores();
- // .AddEntityFrameworkStores().AddDefaultTokenProviders();
+// services.AddIdentityApiEndpoints()
+ // .AddEntityFrameworkStores();
+ // .AddEntityFrameworkStores().AddDefaultTokenProviders();
}
-
+
private void AddApiVersioning(IServiceCollection services)
{
-
services.AddApiVersioning(opt =>
{
opt.ReportApiVersions = true;
@@ -119,25 +173,25 @@ public class AppBootstrap(IConfiguration configuration)
new HeaderApiVersionReader("x-api-version"),
new MediaTypeApiVersionReader("x-api-version"));
});
-
}
+
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
+ Type = SecuritySchemeType.Http,
+ Scheme = "Bearer",
+ BearerFormat = "JWT"
});
var scheme = new OpenApiSecurityRequirement
{
@@ -156,50 +210,62 @@ public class AppBootstrap(IConfiguration configuration)
new List()
}
};
+
options.AddSecurityRequirement(scheme);
- });
- services.AddTransient, SwaggerOptions>();
- services.AddSwaggerGen(options =>
- {
options.OperationFilter();
});
-
+ services.AddTransient, SwaggerOptions>();
+
services.AddVersionedApiExplorer(setup =>
{
setup.GroupNameFormat = "'v'VVV";
setup.SubstituteApiVersionInUrl = true;
});
-
}
public void Configure(WebApplication app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
- app.MapIdentityApi();
- app.MapControllers();
+ app.UseAuthentication();
app.UseAuthorization();
+// app.MapIdentityApi();
+
+ app.MapControllers();
+
app.MapHealthChecks("/health");
-
+
// Configure the HTTP request pipeline.
if (true)
{
- var apiVersionDescriptionProvider = app.Services.GetRequiredService();
- app.UseSwagger();
- app.UseSwaggerUI();
- app.MapSwagger();
- app.UseSwaggerUI(options =>
+ app.UseSwagger(options =>
{
- foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
+ options.PreSerializeFilters.Add((swagger, httpReq) =>
{
- options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
- description.GroupName.ToUpperInvariant());
- }
- });
+ if (httpReq.Headers.ContainsKey("X-Forwarded-Host"))
+ {
+ string basePath;
+ switch (Environment.GetEnvironmentVariable("TYPE")) // httpReq.Host.Value
+ {
+ case "STUB":
+ basePath = "containers/HeartDev-heart_stub";
+ break;
+ case "BDD":
+ basePath = "containers/HeartDev-api";
+ break;
+ default:
+ basePath = httpReq.Host.Value;
+ break;
+ }
+ var serverUrl = $"https://{httpReq.Headers["X-Forwarded-Host"]}/{basePath}";
+ swagger.Servers = new List { new() { Url = serverUrl } };
+ }
+ });
+ });
+ app.UseSwaggerUI();
+ app.MapSwagger();
}
-
-
}
-}
+}
\ No newline at end of file
diff --git a/src/HeartTrackAPI/appsettings.json b/src/HeartTrackAPI/appsettings.json
index 5fb6b3c..17437fa 100644
--- a/src/HeartTrackAPI/appsettings.json
+++ b/src/HeartTrackAPI/appsettings.json
@@ -8,5 +8,10 @@
"AllowedHosts": "*",
"ConnectionStrings": {
"HeartTrackAuthConnection": "Data Source=uca_HeartTrack.db"
- }
+ },
+ "JWT": {
+ "Issuer": "HeartTrack",
+ "Audience": "HeartTrack",
+ "SigningKey": "jythgfbhtgdfytrhdgfythrydfghtrydfythrjgfytjgfjkhljkloijijlijoukhjuhygyhdhcesxeqaewxsfcgevfbtgjnh61689346843njfdvcgdv"
+ }
}
diff --git a/src/Model/EnumMappeur.cs b/src/Model/EnumMappeur.cs
index b939ee9..38ca1a3 100644
--- a/src/Model/EnumMappeur.cs
+++ b/src/Model/EnumMappeur.cs
@@ -9,6 +9,7 @@ public static class EnumMappeur
return value switch
{
"None" => Shared.AthleteOrderCriteria.None,
+ "ById" => Shared.AthleteOrderCriteria.ById,
"ByUsername" => Shared.AthleteOrderCriteria.ByUsername,
"ByFirstName" => Shared.AthleteOrderCriteria.ByFirstName,
"ByLastName" => Shared.AthleteOrderCriteria.ByLastName,
@@ -17,7 +18,7 @@ public static class EnumMappeur
"ByWeight" => Shared.AthleteOrderCriteria.ByWeight,
"ByDateOfBirth" => Shared.AthleteOrderCriteria.ByDateOfBirth,
"ByEmail" => Shared.AthleteOrderCriteria.ByEmail,
- "ByIsCoach" => Shared.AthleteOrderCriteria.ByIsCoach,
+ "ByRole" => Shared.AthleteOrderCriteria.ByRole,
_ => Shared.AthleteOrderCriteria.None
};
}
diff --git a/src/Model/Manager/IDataManager.cs b/src/Model/Manager/IDataManager.cs
index 6fc8532..9a7b6cc 100644
--- a/src/Model/Manager/IDataManager.cs
+++ b/src/Model/Manager/IDataManager.cs
@@ -1,3 +1,4 @@
+using Dto.Tiny;
using Model.Repository;
namespace Model.Manager;
@@ -6,4 +7,6 @@ public interface IDataManager
{
IUserRepository UserRepo { get; }
IActivityRepository ActivityRepo { get; }
+
+ IDataSourceRepository DataSourceRepo { get; }
}
diff --git a/src/Model/Repository/IActivityRepository.cs b/src/Model/Repository/IActivityRepository.cs
index eab97da..2175392 100644
--- a/src/Model/Repository/IActivityRepository.cs
+++ b/src/Model/Repository/IActivityRepository.cs
@@ -1,13 +1,18 @@
+using Dto;
+using Dto.Tiny;
using Shared;
namespace Model.Repository;
public interface IActivityRepository
{
- public Task?> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false);
+ public Task?> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false);
public Task GetActivityByIdAsync(int id);
+ public Task GetActivityById(int id);
public Task AddActivity(Activity activity);
- public Task UpdateActivity(int id, Activity activity);
+ public Task AddActivity(NewActivityDto activity);
+
+ public Task UpdateActivity(int id, ActivityTinyDto activity);
public Task DeleteActivity(int id);
public Task GetNbItems();
public Task?> GetActivitiesByUser(int userId, int index, int count, ActivityOrderCriteria orderCriteria, bool descending= false);
diff --git a/src/Model/Repository/IDataSourceRepository.cs b/src/Model/Repository/IDataSourceRepository.cs
new file mode 100644
index 0000000..6a442ae
--- /dev/null
+++ b/src/Model/Repository/IDataSourceRepository.cs
@@ -0,0 +1,6 @@
+namespace Model.Repository;
+
+public interface IDataSourceRepository
+{
+ Task GetItemById(int id);
+}
\ No newline at end of file
diff --git a/src/Model/Repository/IUserRepository.cs b/src/Model/Repository/IUserRepository.cs
index dddc19d..0541afa 100644
--- a/src/Model/Repository/IUserRepository.cs
+++ b/src/Model/Repository/IUserRepository.cs
@@ -1,14 +1,23 @@
-using Shared;
+using Dto;
+using Dto.Tiny;
+using Shared;
namespace Model.Repository;
-public interface IUserRepository : IGenericRepository
+public interface IUserRepository : IGenericRepository // Make it generic
{
+ // [TODO] [Dave] DELETE it use just in the test
public Task?> GetUsers(int index, int count, AthleteOrderCriteria? criteria , bool descending = false);
- public Task AddFriend(User user, User friend);
- public Task RemoveFriend(User user, User friend);
- public Task?> GetFriends(User user, int index, int count, AthleteOrderCriteria? criteria, bool descending = false);
- public Task GetNbFriends(User user);
+ public Task?> GetUsersTiny(int index, int count, AthleteOrderCriteria? criteria , bool descending = false);
+ public Task AddFollowing(int fromUser, int toUser);
+ public Task RemoveFollowing(int fromUser, int toUser);
+
+ // DELETE
+ public Task?> GetFriends(int user, int index, int count, AthleteOrderCriteria? criteria, bool descending = false);
+ public Task GetNbFriends(int user);
+ public Task UpdateUser(int old,UserTinyDto user);
+ public Task GetUserById(int id);
+ public Task GetUserTinyById(int id);
public Task?> GetAllAthletes(int index, int count, AthleteOrderCriteria? criteria, bool descending = false);
public Task?> GetAllCoaches(int index, int count, AthleteOrderCriteria? criteria, bool descending = false);
diff --git a/src/Model/User.cs b/src/Model/User.cs
index 3a8acd6..ccccf3f 100644
--- a/src/Model/User.cs
+++ b/src/Model/User.cs
@@ -9,7 +9,7 @@ public class User
public string FirstName { get; set; }
public string Email { get; set; }
public string? MotDePasse { get; set; }
- public string Sexe { get; set; }
+ public char Sexe { get; set; }
public float Lenght { get; set; }
public float Weight { get; set; }
public DateTime DateOfBirth { get; set; }
@@ -22,7 +22,7 @@ public class User
public List Users { get; set; } = new List();
public List DataSources { get; set; } = new List();
- public User( string username, string profilePicture, string nom, string prenom, string email, string motDePasse, string sexe, float taille, float poids, DateTime dateNaissance, Role role)
+ public User( string username, string profilePicture, string nom, string prenom, string email, string motDePasse, char sexe, float taille, float poids, DateTime dateNaissance, Role role)
{
Username = username;
ProfilePicture = profilePicture;
diff --git a/src/Model/utils/ActivityAnalysis.cs b/src/Model/utils/ActivityAnalysis.cs
new file mode 100644
index 0000000..1634dbb
--- /dev/null
+++ b/src/Model/utils/ActivityAnalysis.cs
@@ -0,0 +1,63 @@
+using Model.utils;
+
+namespace Dto.Tiny;
+
+public class ActivityAnalysis
+{
+ public double AverageHeartRate { get; private set; }
+ public string AverageHeartRateAdvice { get; private set; }
+ public double Vo2Max { get; private set; }
+ public string Vo2MaxAdvice { get; private set; }
+ public double NormalBpm { get; private set; }
+ public string NormalBpmAdvice { get; private set; }
+ public double HeartRateVariability { get; private set; }
+ public string HeartRateVariabilityAdvice { get; private set; }
+ public string HeartRateZone { get; private set; }
+ public string HeartRateZoneAdvice { get; private set; }
+ public double Duration { get; private set; }
+ public string DurationAdvice { get; private set; }
+ public double Effort { get; private set; }
+ public string EffortAdvice { get; private set; }
+ public static ActivityAnalysis FromActivityData(ResponseActivityDto activity)
+ {
+ double dureeActivity = (activity.EndTime - activity.StartTime).TotalMinutes;
+ var age = DateTime.Today.Year - activity.Athlete.DateOfBirth.Year;
+ var gender = activity.Athlete.Sexe;
+ var poids = activity.Athlete.Weight;
+ var effortFelt = activity.EffortFelt;
+ var averageHeartRate = activity.Average;
+ var heartRateVariability = activity.Variability;
+
+ var heartRateZones = HeartRateAdvise.CalculateHeartRateZones(age);
+
+ var effortScore = HeartRateAdvise.EvaluateEffort(activity);
+
+ var (seuilBPM, vo2Max) = HeartRateAdvise.SeuilBPMavance(gender, age, poids, dureeActivity);
+ string averageHeartRateAdvice = HeartRateAdvise.GenerateAverageHeartRateAdvice(averageHeartRate, seuilBPM);
+ string vo2MaxAdvice = HeartRateAdvise.GenerateVo2MaxAdvice(vo2Max);
+ string normalBpmAdvice = HeartRateAdvise.GenerateNormalBpmAdvice(seuilBPM, averageHeartRate);
+ string hrvAdvice = HeartRateAdvise.GenerateHrvAdvice(heartRateVariability);
+ HeartRateAdvise.HeartRateZone currentZone = heartRateZones.Find(zone => averageHeartRate >= zone.MinHeartRate && averageHeartRate <= zone.MaxHeartRate);
+ string heartRateZoneAdvice = HeartRateAdvise.GenerateHeartRateZoneAdvice(currentZone?.Name);
+ var effortAccuracy = HeartRateAdvise.CompareEffort(effortFelt, (int)effortScore);
+ var analysis = new ActivityAnalysis
+ {
+ AverageHeartRate = averageHeartRate,
+ AverageHeartRateAdvice = averageHeartRateAdvice,
+ Vo2Max = vo2Max,
+ Vo2MaxAdvice = vo2MaxAdvice,
+ NormalBpm = seuilBPM,
+ NormalBpmAdvice = normalBpmAdvice,
+ HeartRateVariability = heartRateVariability,
+ HeartRateVariabilityAdvice = hrvAdvice,
+ HeartRateZone = currentZone != null ? currentZone.Name : "N/A",
+ HeartRateZoneAdvice = heartRateZoneAdvice,
+ Duration = dureeActivity,
+ DurationAdvice =HeartRateAdvise.GenerateDurationAdvice(dureeActivity),
+ Effort = effortScore,
+ EffortAdvice = HeartRateAdvise.GenerateEffortAdvice(effortAccuracy)
+ };
+
+ return analysis;
+ }
+}
\ No newline at end of file
diff --git a/src/Model/utils/HeartRateAdvise.cs b/src/Model/utils/HeartRateAdvise.cs
new file mode 100644
index 0000000..1439f12
--- /dev/null
+++ b/src/Model/utils/HeartRateAdvise.cs
@@ -0,0 +1,278 @@
+using Dto;
+
+namespace Model.utils;
+
+public class HeartRateAdvise
+{
+ public class HeartRateZone
+ {
+ public string Name { get; set; }
+ public int MinHeartRate { get; set; }
+ public int MaxHeartRate { get; set; }
+ }
+
+ public string getAdvise(Activity activity)
+ {
+ return "You should take a break";
+ }
+ public static List CalculateHeartRateZones(int age)
+ {
+ int fcm = 220 - age; // Estimation de la FCM
+ List zones = new List
+ {
+ new HeartRateZone { Name = "Très Légère", MinHeartRate = (int)(fcm * 0.50), MaxHeartRate = (int)(fcm * 0.60) },
+ new HeartRateZone { Name = "Aérobie légère", MinHeartRate = (int)(fcm * 0.60), MaxHeartRate = (int)(fcm * 0.70) },
+ new HeartRateZone { Name = "Aérobie", MinHeartRate = (int)(fcm * 0.70), MaxHeartRate = (int)(fcm * 0.80) },
+ new HeartRateZone { Name = "Anaérobie", MinHeartRate = (int)(fcm * 0.80), MaxHeartRate = (int)(fcm * 0.90) },
+ new HeartRateZone { Name = "Maximum", MinHeartRate = (int)(fcm * 0.90), MaxHeartRate = (int)(fcm * 1.00) }
+ };
+
+ return zones;
+ }
+ public static double EvaluateEffort(ResponseActivityDto stats)
+ {
+ double score = 0;
+
+ score += stats.Average * 0.3; // Exemple de poids
+ score += stats.HeartRates.Average(speed => speed.Speed ?? 0) * 0.25;
+ score += stats.HeartRates.Sum(dist => dist.Distance ?? 0) * 0.15;
+ score += stats.HeartRates.Average(hr => hr.Cadence ?? 0) * 0.1;
+ score += stats.HeartRates.Average(hr => hr.Power ?? 0) * 0.15;
+ score += stats.StandardDeviation * 0.05;
+
+ return score;
+ }
+
+ public static string CompareEffort(int perceivedEffort, int objectiveEffort)
+ {
+ if (perceivedEffort == objectiveEffort) return "Accurate";
+ if (perceivedEffort > objectiveEffort) return "Overestimated";
+ if (perceivedEffort < objectiveEffort) return "Underestimated";
+ return "Unknown";
+ }
+
+
+// Faible intensité : 50-60% de la FCM
+// Intensité modérée : 60-70% de la FCM
+// Haute intensité : 70-85% de la FCM
+ public static double CalculerVo2Max(char sexe, int age, double poids)
+ {
+ if (sexe == 'M')
+ {
+ return (poids * 0.198) + (age * -0.193) + 24.489;
+ }
+
+ // sexe == "F"
+ {
+ return (poids * 0.201) + (age * -0.217) + 20.453;
+ }
+ }
+
+ public static (double SeuilBPM, double Vo2Max) SeuilBPMavance(char sexe, int age, double poids, double dureeActivite)
+ {
+ double fcm = sexe == 'M' ? 207 - (0.7 * age) : 206 - (0.88 * age);
+ double vo2Max = CalculerVo2Max(sexe, age, poids);
+
+ double zone = dureeActivite <= 30 ? 0.8 : dureeActivite <= 60 ? 0.7 : 0.6;
+ double seuilBpm = fcm * zone;
+
+ return (seuilBpm, vo2Max);
+ }
+
+ public static Dictionary GenerateAdvice(ResponseActivityDto activity)
+ {
+ int dureeActivity = (activity.EndTime - activity.StartTime).Minutes;
+ var age = DateTime.Today.Year - activity.Athlete.DateOfBirth.Year;
+ var gender = activity.Athlete.Sexe;
+ var poids = activity.Athlete.Weight;
+ var effortFelt = activity.EffortFelt;
+ var averageHeartRate = activity.Average;
+ var heartRateVariability = activity.Variability;
+ var heartRateZones = CalculateHeartRateZones(age);
+ var effortScore = EvaluateEffort(activity);
+
+
+ var (seuilBPM, vo2Max) = SeuilBPMavance(gender, age, poids, dureeActivity);
+ Dictionary healthMetrics = new Dictionary();
+
+ // Conseil pour AverageHeartRate
+ string averageHeartRateAdvice = averageHeartRate > seuilBPM
+ ? "Votre rythme cardiaque moyen est supérieur au seuil recommandé. Envisagez de réduire l'intensité."
+ : "Votre rythme cardiaque moyen est dans une bonne plage. Continuez à maintenir votre intensité actuelle.";
+
+ // Conseil pour Vo2Max
+ string vo2MaxAdvice;
+ if (vo2Max < 30)
+ vo2MaxAdvice =
+ "Votre Vo2 max est faible, envisagez d'augmenter progressivement l'intensité de vos entraînements.";
+ else if (vo2Max < 40)
+ vo2MaxAdvice = "Votre Vo2 max est dans la moyenne. Continuez de travailler sur votre endurance.";
+ else vo2MaxAdvice = "Votre Vo2 max est excellente. Vous pourriez bénéficier d'entraînements à haute intensité.";
+
+ // Conseil basé sur la comparaison avec NormalBpm
+ string normalBpmAdvice = seuilBPM > averageHeartRate
+ ? "Votre BPM normal est plus élevé que la moyenne, assurez-vous de surveiller votre intensité d'entraînement."
+ : "Votre BPM normal est inférieur à la moyenne, ce qui peut indiquer un bon niveau de forme physique.";
+ // Conseil pour HeartRateVariability
+ string hrvAdvice = heartRateVariability < 40
+ ? "Votre HRV est basse, ce qui peut indiquer un stress élevé ou une récupération insuffisante. Envisagez d'améliorer votre récupération."
+ : heartRateVariability < 60
+ ? "Votre HRV est dans une plage moyenne. Continuez de surveiller votre récupération et votre stress."
+ : "Votre HRV est élevée, indiquant une bonne récupération et une gestion du stress. Continuez vos bonnes pratiques de gestion de la santé.";
+
+ HeartRateZone currentZone = heartRateZones.Find(zone =>
+ averageHeartRate >= zone.MinHeartRate && averageHeartRate <= zone.MaxHeartRate);
+ string heartRateZoneAdvice;
+ if (currentZone != null)
+ {
+ heartRateZoneAdvice = $"Votre BPM moyen est dans la zone '{currentZone.Name}', qui est idéale pour ";
+ switch (currentZone.Name)
+ {
+ case "Repos":
+ heartRateZoneAdvice += "favoriser la récupération.";
+ break;
+ case "Endurance":
+ heartRateZoneAdvice += "améliorer l'endurance cardiovasculaire et brûler des graisses.";
+ break;
+ case "Aérobie":
+ heartRateZoneAdvice += "améliorer votre capacité aérobie.";
+ break;
+ case "Anaérobie":
+ heartRateZoneAdvice += "améliorer la performance à haute intensité.";
+ break;
+ case "VO2 Max":
+ heartRateZoneAdvice += "maximiser la performance et la capacité aérobie.";
+ break;
+ default:
+ heartRateZoneAdvice =
+ "Cette zone est spécifique et peut avoir différents objectifs en fonction de votre plan d'entraînement.";
+ break;
+ }
+ }
+ else
+ {
+ heartRateZoneAdvice =
+ "Vous n'êtes dans aucune zone spécifique. Assurez-vous que votre BPM moyen est correctement mesuré.";
+ }
+
+ // Comparaison de l'effort objectif avec l'effort perçu
+ var effortAccuracy = CompareEffort(effortFelt, (int)effortScore);
+
+ // Remplissage du dictionnaire
+ healthMetrics.Add("AverageHeartRate", (averageHeartRate, averageHeartRateAdvice));
+ healthMetrics.Add("Vo2Max", (vo2Max, vo2MaxAdvice));
+ healthMetrics.Add("NormalBpm", (seuilBPM, normalBpmAdvice));
+ healthMetrics.Add("HeartRateVariability", (heartRateVariability, hrvAdvice));
+ healthMetrics.Add("HeartRateZone", (averageHeartRate, heartRateZoneAdvice));
+ healthMetrics.Add("Duration",
+ (dureeActivity,
+ dureeActivity < 75
+ ? "Vous pourriez augmenter la durée de vos activités pour atteindre les recommandations."
+ : "Votre durée d'activité est conforme aux recommandations."));
+ healthMetrics.Add("Effort", (effortScore, GenerateEffortAdvice(effortAccuracy)));
+ return healthMetrics;
+ }
+ public static string GenerateAverageHeartRateAdvice(float averageHeartRate, double seuilBPM)
+ {
+ return averageHeartRate > seuilBPM
+ ? "Votre rythme cardiaque moyen est supérieur au seuil recommandé. Envisagez de réduire l'intensité."
+ : "Votre rythme cardiaque moyen est dans une bonne plage. Continuez à maintenir votre intensité actuelle.";
+ }
+ public static string GenerateVo2MaxAdvice(double vo2Max)
+ {
+ if (vo2Max < 30)
+ return "Votre Vo2 max est faible, envisagez d'augmenter progressivement l'intensité de vos entraînements.";
+ if (vo2Max < 40)
+ return "Votre Vo2 max est dans la moyenne. Continuez de travailler sur votre endurance.";
+ return "Votre Vo2 max est excellente. Vous pourriez bénéficier d'entraînements à haute intensité.";
+ }
+ public static string GenerateNormalBpmAdvice(double normalBpm,double averageHeartRate)
+ {
+ return normalBpm > averageHeartRate
+ ? "Votre BPM normal est plus élevé que la moyenne, ce qui peut indiquer un bon niveau de forme physique."
+ : "Votre BPM normal est inférieur à la moyenne, assurez-vous de surveiller votre intensité d'entraînement.";
+ }
+
+ public static string GenerateHrvAdvice(double heartRateVariability)
+ {
+ return heartRateVariability < 40
+ ? "Votre HRV est basse, ce qui peut indiquer un stress élevé ou une récupération insuffisante. Envisagez d'améliorer votre récupération."
+ : heartRateVariability < 60
+ ? "Votre HRV est dans une plage moyenne. Continuez de surveiller votre récupération et votre stress."
+ : "Votre HRV est élevée, indiquant une bonne récupération et une gestion du stress. Continuez vos bonnes pratiques de gestion de la santé.";
+ }
+ public static string GenerateHeartRateZoneAdvice(string? heartRateZone)
+ {
+ return heartRateZone switch
+ {
+ "Repos" => "Favoriser la récupération.",
+ "Endurance" => "Améliorer l'endurance cardiovasculaire et brûler des graisses.",
+ "Aérobie" => "Améliorer votre capacité aérobie.",
+ "Anaérobie" => "Améliorer la performance à haute intensité.",
+ "VO2 Max" => "Maximiser la performance et la capacité aérobie.",
+ _ => "Cette zone est spécifique et peut avoir différents objectifs en fonction de votre plan d'entraînement."
+ };
+ }
+ public static string GenerateDurationAdvice(double dureeActivity)
+ {
+ return dureeActivity < 75
+ ? "Vous pourriez augmenter la durée de vos activités pour atteindre les recommandations."
+ : "Votre durée d'activité est conforme aux recommandations.";
+ }
+
+ public static string GenerateEffortAdvice(string effortAccuracy)
+ {
+ return effortAccuracy switch
+ {
+ "Accurate" => "Votre perception de l'effort est précise. Continuez ainsi!",
+ "Overestimated" => "Vous surestimez votre effort. Essayez de pousser un peu plus lors de vos prochaines activités.",
+ "Underestimated" => "Vous sous-estimez votre effort. Vous pourriez être plus proche de vos limites que vous ne le pensez.",
+ _ => "Continuez à surveiller et ajuster votre effort pour de meilleurs résultats."
+ };
+ }
+}
+
+/*
+public class EffortAnalysisResult
+{
+ public string EffortAccuracy { get; set; }
+ public string Advice { get; set; }
+}
+
+public EffortAnalysisResult AnalyzeActivityEffort(ResponseActivityDto activity)
+{
+
+
+ var effortScore = EvaluateEffort(activity);
+
+ // Comparaison de l'effort objectif avec l'effort perçu
+ var effortAccuracy = CompareEffort(activity.EffortFelt, (int)effortScore);
+ var result = new EffortAnalysisResult
+ {
+ EffortAccuracy = effortAccuracy,
+ Advice = GenerateEffortAdvice(effortAccuracy)
+ };
+
+ return result;
+}
+public List GetMockHeartRateData()
+ {
+ var random = new Random();
+ return Enumerable.Range(1, 3600)
+ .Select(_ => new HeartRateTinyDto
+ {
+ HeartRate = random.Next(60, 220),
+ Timestamp = new DateTime(2021, 1, 1).AddSeconds(random.Next(3600)),
+ Latitude = random.NextDouble() * 180 - 90,
+ Longitude = random.NextDouble() * 360 - 180,
+ Altitude = random.NextDouble() * 1000,
+ Cadence = random.Next(60, 120),
+ Distance = random.NextDouble() * 100,
+ Speed = random.NextDouble() * 30,
+ Power = random.Next(0, 500),
+ Temperature = random.NextDouble() * 30
+ }).ToList();
+ }
+
+*/
+
diff --git a/src/Model2Entities/ActivityRepository.cs b/src/Model2Entities/ActivityRepository.cs
index d3ba2da..a53c22f 100644
--- a/src/Model2Entities/ActivityRepository.cs
+++ b/src/Model2Entities/ActivityRepository.cs
@@ -1,3 +1,5 @@
+using Dto;
+using Dto.Tiny;
using Model;
using Model.Repository;
using Shared;
@@ -5,6 +7,7 @@ using Model.Manager;
using Microsoft.Extensions.Logging;
using Entities;
using EFMappers;
+using Entities2Dto;
using Microsoft.EntityFrameworkCore;
namespace Model2Entities;
@@ -21,13 +24,13 @@ public partial class DbDataManager : IDataManager
this._logger = logger;
}
- public async Task> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false)
+ public async Task?> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false)
{
_logger.LogInformation($"GetActivities with index {index} and count {count}", index, count);
_logger.LogInformation($"GetActivities with criteria {criteria} and descending {descending}", criteria, descending);
var activities = _dataManager.DbContext.ActivitiesSet
- .IncludeStandardProperties().GetItemsWithFilterAndOrdering(b => true, index, count, criteria, descending).ToModels();
+ .GetItemsWithFilterAndOrdering(b => true, index, count, criteria, descending).ToTinyDtos();
_logger.LogInformation($"Retrieved {activities.Count()} activities");
return await Task.FromResult(activities);
@@ -47,6 +50,20 @@ public partial class DbDataManager : IDataManager
return await Task.FromResult(activity);
}
+ public async Task GetActivityById(int id)
+ {
+ _logger.LogInformation($"GetActivityByIdAsync with id {id}", id);
+
+ var activityEntity = await _dataManager.DbContext.ActivitiesSet.IncludeAll(_dataManager.DbContext).SingleOrDefaultAsync(a => a.IdActivity == id);
+ var activity = activityEntity != null ? activityEntity.ToResponseDto() : null;
+
+ if (activity != null)
+ _logger.LogInformation($"Retrieved activity with ID {id}");
+ else
+ _logger.LogWarning($"No activity found with ID {id}");
+
+ return await Task.FromResult(activity);
+ }
public async Task AddActivity(Activity activity)
{
@@ -67,18 +84,46 @@ public partial class DbDataManager : IDataManager
}
}
- public async Task UpdateActivity(int id, Activity activity)
+ public async Task AddActivity(NewActivityDto activity)
+ {
+ try
+ {
+ _logger.LogInformation("Adding new activity");
+
+ var addedActivity = activity.Activity.ToEntity();
+ addedActivity.DataSourceId = activity.DataSourceId;
+ addedActivity.AthleteId = activity.AthleteId;
+ addedActivity.HeartRates = activity.HeartRates.ToEntities().ToList();
+
+ await _dataManager.DbContext.AddItem(addedActivity);
+ _logger.LogInformation($"Added activity with ID {addedActivity.IdActivity}");
+ _dataManager.DbContext.SaveChanges();
+
+ return await Task.FromResult(addedActivity.ToResponseDto());
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error occurred while adding activity");
+ throw;
+ }
+ }
+
+ public async Task UpdateActivity(int id, ActivityTinyDto activity)
{
try
{
- _logger.LogInformation($"Updating activity with ID {id}");
- var updatedActivity = await _dataManager.DbContext.UpdateItem(id, activity, (activity, entity) =>
+ var entity =await _dataManager.DbContext.ActivitiesSet.IncludeAll(_dataManager.DbContext)
+ .FirstOrDefaultAsync(a => a.IdActivity == id);
+
+ if (entity != null)
{
+
+ _logger.LogInformation($"Updating activity with ID {id}");
entity.Type = activity.Type;
entity.Date = DateOnly.FromDateTime(activity.Date);
entity.StartTime = TimeOnly.FromDateTime(activity.StartTime);
entity.EndTime = TimeOnly.FromDateTime(activity.EndTime);
- entity.EffortFelt = activity.Effort;
+ entity.EffortFelt = activity.EffortFelt;
entity.Variability = activity.Variability;
entity.Variance = activity.Variance;
entity.StandardDeviation = activity.StandardDeviation;
@@ -87,16 +132,15 @@ public partial class DbDataManager : IDataManager
entity.Minimum = activity.Minimum;
entity.AverageTemperature = activity.AverageTemperature;
entity.HasAutoPause = activity.HasAutoPause;
- });
- if (updatedActivity != null)
- {
+ _dataManager.DbContext.SaveChanges();
+
_logger.LogInformation($"Updated activity with ID {id}");
- return await Task.FromResult(updatedActivity.ToModel());
+ return await Task.FromResult(entity.ToResponseDto());
}
else
{
_logger.LogError($"Failed to update activity with ID {id}");
- return await Task.FromResult(null);
+ return await Task.FromResult(null);
}
}
catch (Exception ex)
diff --git a/src/Model2Entities/DataSourceRepository.cs b/src/Model2Entities/DataSourceRepository.cs
new file mode 100644
index 0000000..4630d12
--- /dev/null
+++ b/src/Model2Entities/DataSourceRepository.cs
@@ -0,0 +1,34 @@
+using Dto.Tiny;
+using Entities2Dto;
+using Microsoft.Extensions.Logging;
+using Model.Repository;
+
+namespace Model2Entities;
+
+public partial class DbDataManager
+{
+ public class DataSourceRepository : IDataSourceRepository
+ {
+ private readonly DbDataManager _dataManager;
+ private readonly ILogger _logger;
+
+ public DataSourceRepository(DbDataManager dbDataManager, ILogger logger)
+ {
+ _dataManager = dbDataManager;
+ _logger = logger;
+ }
+
+
+ public async Task GetItemById(int id)
+ {
+ var dataSource = await _dataManager.DbContext.DataSourcesSet.FindAsync(id);
+ if (dataSource == null)
+ {
+ _logger.LogInformation($"DataSource with ID {id} not found.");
+ return null;
+ }
+
+ return dataSource.ToTinyDto();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Model2Entities/DbDataManager.cs b/src/Model2Entities/DbDataManager.cs
index bf1e4fd..3968739 100644
--- a/src/Model2Entities/DbDataManager.cs
+++ b/src/Model2Entities/DbDataManager.cs
@@ -1,4 +1,5 @@
using DbContextLib;
+using Dto.Tiny;
using EFMappers;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@@ -11,6 +12,8 @@ public partial class DbDataManager: IDataManager
{
public IActivityRepository ActivityRepo { get; }
public IUserRepository UserRepo { get; }
+
+ public IDataSourceRepository DataSourceRepo { get; }
protected HeartTrackContext DbContext { get; }
protected readonly ILogger _logger = new Logger(new LoggerFactory());
@@ -21,7 +24,9 @@ public partial class DbDataManager: IDataManager
DbContext = dbContext;
ActivityRepo = new ActivityRepository(this, _logger);
UserRepo = new UserRepository(this, _logger);
+ DataSourceRepo = new DataSourceRepository(this, _logger);
ActivityMapper.Reset();
+ UserMappeur.Reset();
// Faire pour les autres reset() des autres mappers
}
@@ -34,5 +39,6 @@ public partial class DbDataManager: IDataManager
DbContext = new HeartTrackContext();
ActivityRepo = new ActivityRepository(this, _logger);
UserRepo= new UserRepository(this, _logger);
+ DataSourceRepo = new DataSourceRepository(this, _logger);
}
}
diff --git a/src/Model2Entities/Extension.cs b/src/Model2Entities/Extension.cs
index d7341ec..9d2bcf4 100644
--- a/src/Model2Entities/Extension.cs
+++ b/src/Model2Entities/Extension.cs
@@ -40,13 +40,10 @@ public static class Extensions
var existingT = await context.Set().FindAsync(id);
if (existingT != null && newItem != null)
{
- // Appliquer les mises à jour sur l'objet existant en utilisant l'action passée en paramètre
updateAction(newItem, existingT);
- // Marquer l'objet comme modifié dans le contexte
context.Update(existingT);
-
- // Enregistrer les modifications dans la base de données
+
await context.SaveChangesAsync();
return existingT;
}
@@ -84,7 +81,7 @@ public static class Extensions
return descending ? list.OrderByDescending(x => propertyInfo.GetValue(x)) : list.OrderBy(x => propertyInfo.GetValue(x));
}
- public static IQueryable IncludeAll(this HeartTrackContext dbContext, IQueryable query) where TEntity : class
+ public static IQueryable IncludeAll(this IQueryable query, HeartTrackContext dbContext) where TEntity : class
{
var entityType = dbContext.Model.FindEntityType(typeof(TEntity));
foreach (var navigation in entityType.GetNavigations())
diff --git a/src/Model2Entities/Model2Entities.csproj b/src/Model2Entities/Model2Entities.csproj
index d1f7751..24879a9 100644
--- a/src/Model2Entities/Model2Entities.csproj
+++ b/src/Model2Entities/Model2Entities.csproj
@@ -8,6 +8,7 @@
+
diff --git a/src/Model2Entities/UserRepository.cs b/src/Model2Entities/UserRepository.cs
index 09d2cf2..e27b9af 100644
--- a/src/Model2Entities/UserRepository.cs
+++ b/src/Model2Entities/UserRepository.cs
@@ -1,9 +1,12 @@
+using Dto;
+using Dto.Tiny;
using Microsoft.Extensions.Logging;
using Model;
using Model.Repository;
using Shared;
using EFMappers;
using Entities;
+using Entities2Dto;
using Microsoft.EntityFrameworkCore;
namespace Model2Entities;
@@ -39,12 +42,22 @@ public partial class DbDataManager
}
+ public async Task?> GetUsersTiny(int index, int count, AthleteOrderCriteria? criteria, bool descending = false)
+ {
+
+ var users = _dataManager.DbContext.AthletesSet.GetItemsWithFilterAndOrdering(b => true,
+ index, count,
+ criteria != AthleteOrderCriteria.None ? criteria : null, descending);
+ _logger.LogInformation($"Retrieved {users.Count()} users");
+ return await Task.FromResult(users.ToTinyDtos());
+ }
+
public async Task GetItemById(int id)
{
_logger.LogInformation($"GetItemById with id {id}", id);
var userEntity = await _dataManager.DbContext.AthletesSet.IncludeStandardProperties()
- .SingleOrDefaultAsync(a => a.IdAthlete == id);
+ .SingleOrDefaultAsync(a => a.Id == id);
var user = userEntity != null ? userEntity.ToModel() : null;
if (user != null)
_logger.LogInformation($"Retrieved user with ID {id}");
@@ -54,6 +67,55 @@ public partial class DbDataManager
}
+ public async Task UpdateUser(int old, UserTinyDto user)
+ {
+ _logger.LogInformation($"UpdateUser with id {old}", old);
+ var originalEntity = _dataManager.DbContext.AthletesSet.Find(old);
+ if (originalEntity == null)
+ {
+ _logger.LogWarning($"No user found with ID {old}");
+ return await Task.FromResult(null);
+ }
+ var originalEntry = _dataManager.DbContext.Entry(originalEntity);
+ var values = typeof(AthleteEntity).GetProperties().Where(ppty => ppty.Name != "IdAthlete")
+ .ToDictionary(ppty => ppty.Name, ppty => ppty.GetValue(user.ToEntity()));
+ originalEntry.CurrentValues.SetValues(values);
+ _dataManager.DbContext.AthletesSet.Attach(originalEntity);
+ _dataManager.DbContext.Entry(originalEntity).State = EntityState.Modified;
+ _dataManager.DbContext.SaveChanges();
+ var updatedUser = originalEntity.ToTinyDto();
+ if (updatedUser != null)
+ _logger.LogInformation($"Updated user with ID {old}");
+ else
+ _logger.LogWarning($"No user found with ID {old}");
+ return await Task.FromResult(updatedUser);
+ }
+
+ public async Task GetUserById(int id)
+ {
+ _logger.LogInformation($"GetTinyItemById with id {id}", id);
+ var userEntity = await _dataManager.DbContext.AthletesSet.IncludeStandardProperties().Include(a => a.Followers).Include(a => a.Followings)
+ .SingleOrDefaultAsync(a => a.Id == id);
+ var user = userEntity != null ? userEntity.ToResponseDto() : null;
+ if (user != null)
+ _logger.LogInformation($"Retrieved user with ID {id}");
+ else
+ _logger.LogWarning($"No user found with ID {id}");
+ return user;
+ }
+
+ public Task GetUserTinyById(int id)
+ {
+ _logger.LogInformation($"GetTinyItemById with id {id}", id);
+ var userEntity = _dataManager.DbContext.AthletesSet.FindAsync(id).Result;
+ var user = userEntity != null ? userEntity.ToTinyDto() : null;
+ if (user != null)
+ _logger.LogInformation($"Retrieved user with ID {id}");
+ else
+ _logger.LogWarning($"No user found with ID {id}");
+ return Task.FromResult(user);
+ }
+
public async Task UpdateItem(int oldItem, User newItem)
{
_logger.LogInformation($"UpdateItem with id {oldItem}", oldItem);
@@ -107,7 +169,6 @@ public partial class DbDataManager
public async Task GetNbItems()
{
-
_logger.LogInformation("GetNbItems");
var nbItems = await _dataManager.DbContext.AthletesSet.CountAsync();
_logger.LogInformation($"Retrieved {nbItems} users");
@@ -142,85 +203,136 @@ public partial class DbDataManager
return await Task.FromResult(coaches);
}
+
- public async Task AddFriend(User user, User friend)
+ public async Task AddFollowing(int fromUser, int toUser)
{
- _logger.LogInformation($"Attempting to add friend: User {user.Id} adding Friend {friend.Id}");
- var userEntity = _dataManager.DbContext.AthletesSet.IncludeStandardProperties().FirstOrDefault(a => a.IdAthlete == user.Id);
- var friendEntity = _dataManager.DbContext.AthletesSet.IncludeStandardProperties().FirstOrDefault(a => a.IdAthlete == friend.Id);
- if (userEntity == null || friendEntity == null)
+ _logger.LogInformation($"Attempting to add following: User {fromUser} adding Following {toUser}");
+
+ var userEntity = _dataManager.DbContext.AthletesSet
+ .Include(a => a.Followings)
+ .FirstOrDefault(a => a.Id == fromUser);
+
+ if (userEntity == null)
{
- _logger.LogWarning($"User or friend not found: User {user.Id}, Friend {friend.Id}");
- return false;
+ _logger.LogWarning($"User not found: User {fromUser}");
+ throw new FriendShipException("User with id " + fromUser + " not found");
}
- if (userEntity.Followings.All(f => f.FollowingId != friend.Id))
+ if (userEntity.Followings.Any(f => f.FollowingId == toUser))
{
- userEntity.Followings.Add(new FriendshipEntity
- { FollowingId = friend.Id, FollowerId = user.Id, StartDate = DateTime.Now });
- await _dataManager.DbContext.SaveChangesAsync();
- _logger.LogInformation($"Successfully added friend: User {user.Id} added Friend {friend.Id}");
- return true;
+ _logger.LogInformation($"Following already exists: User {fromUser} and Following {toUser}");
+ throw new FriendShipException("Following already exists");
}
+ await _dataManager.DbContext.SaveChangesAsync();
+
+ userEntity.Followings.Add(new FriendshipEntity
+ {
+ FollowingId = toUser,
+ FollowerId = fromUser,
+ StartDate = DateTime.Now
+ });
- _logger.LogInformation($"Friendship already exists: User {user.Id} and Friend {friend.Id}");
- return false;
+ await _dataManager.DbContext.SaveChangesAsync();
+
+ _logger.LogInformation($"Successfully following: from User {fromUser} to Following {toUser}");
+ return true;
}
+
+ public async Task RemoveFollowing(int fromUser, int toUser){
+ _logger.LogInformation($"Attempting to remove following: User {fromUser} removing Following {toUser}");
- public async Task RemoveFriend(User user, User friend)
- {
- _logger.LogInformation($"Attempting to remove friend: User {user.Id} removing Friend {friend.Id}");
- var userEntity = _dataManager.DbContext.AthletesSet.IncludeStandardProperties().FirstOrDefault(a => a.IdAthlete == user.Id);
- var friendEntity = _dataManager.DbContext.AthletesSet.IncludeStandardProperties().FirstOrDefault(a => a.IdAthlete == friend.Id);
- if (userEntity == null || friendEntity == null)
+ var userEntity = _dataManager.DbContext.AthletesSet
+ .Include(a => a.Followings)
+ .FirstOrDefault(a => a.Id == fromUser);
+
+ if (userEntity == null)
{
- _logger.LogWarning($"User or friend not found: User {user.Id}, Friend {friend.Id}");
- return false;
+ _logger.LogWarning($"User not found: User {fromUser}");
+ throw new FriendShipException("User with id " + fromUser + " not found");
}
- var friendship = userEntity.Followings.FirstOrDefault(f => f.FollowingId == friend.Id);
- if (friendship != null)
+ var friendship = userEntity.Followings.FirstOrDefault(f => f.FollowingId == toUser);
+ if (friendship == null)
{
- userEntity.Followings.Remove(friendship);
- await _dataManager.DbContext.SaveChangesAsync();
- _logger.LogInformation($"Successfully removed friend: User {user.Id} removed Friend {friend.Id}");
- return true;
+ _logger.LogInformation($"Following not found: User {fromUser} and Following {toUser}");
+ throw new FriendShipException("Following not found");
}
+ await _dataManager.DbContext.SaveChangesAsync();
- _logger.LogInformation($"Friendship does not exist: User {user.Id} and Friend {friend.Id}");
- return false;
- }
+ userEntity.Followings.Remove(friendship);
- public Task> GetFriends(User user, int index, int count, AthleteOrderCriteria? criteria,
+ await _dataManager.DbContext.SaveChangesAsync();
+
+ _logger.LogInformation($"Successfully removed following: from User {fromUser} to Following {toUser}");
+ return await Task.FromResult(true);
+ }
+ public async Task?> GetFriends(int userId, int index, int count, AthleteOrderCriteria? criteria,
bool descending = false)
{
try
{
- _logger.LogInformation($"GetFriends with index {index} and count {count}", index, count);
- _logger.LogInformation($"GetFriends with criteria {criteria} and descending {descending}", criteria,
- descending);
- var friends = _dataManager.DbContext.AthletesSet.IncludeStandardProperties().Include(a => a.Followers).Include(a => a.Followings)
- .GetItemsWithFilterAndOrdering(a => a.Followers.Any(f => f.FollowingId == user.Id), index, count,
- criteria, descending).ToModels();
- _logger.LogInformation($"Retrieved {friends.Count()} friends");
- return Task.FromResult(friends);
+ _logger.LogInformation($"GetFriends called with index {index}, count {count}, criteria {criteria}, and descending {descending}");
+
+ var athlete = await _dataManager.DbContext.AthletesSet
+ .Include(a => a.Followers).ThenInclude(f => f.Follower)
+ .Include(a => a.Followings).ThenInclude(f => f.Following)
+ .FirstOrDefaultAsync(a => a.Id == userId); // Use async version for better performance
+
+ if (athlete == null)
+ {
+ _logger.LogError("Athlete with id {id} not found", userId);
+ throw new ModelNotFoundException($"Athlete with id {userId} not found");
+ }
+
+ var friendsDtos = athlete.Followings
+ .Where(f => athlete.Followers.Any(ff => ff.FollowerId == f.FollowingId))
+ .Select(f => f.Following).GetItemsWithFilterAndOrdering(a => true, index, count,
+ criteria != AthleteOrderCriteria.None ? criteria : null, descending).ToTinyDtos();
+
+ var userTinyDtos = friendsDtos.ToArray();
+ _logger.LogInformation($"Retrieved {userTinyDtos.Count()} friends for user {userId}");
+
+ return userTinyDtos;
}
catch (Exception ex)
{
_logger.LogError(ex.Message, ex.InnerException, ex.StackTrace);
- return Task.FromResult>(new List