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 @@ +
+ ---   ![C#](https://img.shields.io/badge/C%23-000?style=for-the-badge&logo=c-sharp&logoColor=white&color=purple)   ![Entity Framework](https://img.shields.io/badge/Entity_Framework-000?style=for-the-badge&logo=.net&logoColor=white&color=blue) @@ -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: * [![Git](https://img.shields.io/badge/Versioning-Git-000?style=for-the-badge&logo=git&logoColor=white&color=red)](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 ![.NET](https://img.shields.io/badge/Langage-.NET-000?style=for-the-badge&logo=.net&logoColor=white&color=blue) 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()); + return null; } } - public Task GetNbFriends(User user) + public async Task GetNbFriends(int userId) { - - _logger.LogInformation($"GetNbFriends with user {user}", user); - var nbFriends = _dataManager.DbContext.AthletesSet - .GetItemsWithFilterAndOrdering(a => a.IdAthlete == user.Id, 0, int.MaxValue, - AthleteOrderCriteria.None, false).First().Followings.Count(); - _logger.LogInformation($"Retrieved {nbFriends} friends"); - return Task.FromResult(nbFriends); + try + { + _logger.LogInformation($"GetNbFriends called for user {userId}"); + + 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); + + if (athlete == null) + { + _logger.LogError("Athlete with id {id} not found", userId); + throw new ModelNotFoundException($"Athlete with id {userId} not found"); + } + + // Count the number of mutual friendships + var nbFriends = athlete.Followings + .Count(f => athlete.Followers.Any(ff => ff.FollowerId == f.FollowingId)); + + _logger.LogInformation($"User {userId} has {nbFriends} friends"); + + return nbFriends; + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while counting friends for user {UserId}", userId); + throw; // Consider handling the exception outside of this method or logging it accordingly. + } } + } } \ No newline at end of file diff --git a/src/Shared/AthleteOrderCriteria.cs b/src/Shared/AthleteOrderCriteria.cs index 4bc54f0..3faa935 100644 --- a/src/Shared/AthleteOrderCriteria.cs +++ b/src/Shared/AthleteOrderCriteria.cs @@ -3,43 +3,16 @@ public enum AthleteOrderCriteria { None, + ById, ByUsername, ByFirstName, ByLastName, + ByEmail, BySexe, ByLenght, ByWeight, ByDateOfBirth, - ByEmail, - ByIsCoach + ByRole } } - - -/*public AthleteOrderCriteria MapToAthleteOrderCriteria(string orderingPropertyName) - { - switch (orderingPropertyName) - { - case nameof(User.Username): - return AthleteOrderCriteria.ByUsername; - case nameof(User.FirstName): - return AthleteOrderCriteria.ByFirstName; - case nameof(User.LastName): - return AthleteOrderCriteria.ByLastName; - case nameof(User.Sexe): - return AthleteOrderCriteria.BySexe; - case nameof(User.Length): - return AthleteOrderCriteria.ByLength; - case nameof(User.Weight): - return AthleteOrderCriteria.ByWeight; - case nameof(User.DateOfBirth): - return AthleteOrderCriteria.ByDateOfBirth; - case nameof(User.Email): - return AthleteOrderCriteria.ByEmail; - case nameof(User.IsCoach): - return AthleteOrderCriteria.ByIsCoach; - default: - return AthleteOrderCriteria.None; - } - }*/ \ No newline at end of file diff --git a/src/Shared/Extension.cs b/src/Shared/Extension.cs index d86ba4d..35a832c 100644 --- a/src/Shared/Extension.cs +++ b/src/Shared/Extension.cs @@ -2,25 +2,25 @@ namespace Shared; public static class Extensions { - public static U ToU(this T t, GenericMapper mapper, Func func,Action? action = null) where U :class where T :class + public static U ToU(this T t, GenericMapper mapper, Func func,Action? action = null,bool useMapper = true) where U :class where T :class { var res = mapper.GetU(t); if (res != null) return res; U u = func(t); - mapper.Add(t, u); + if(useMapper) mapper.Add(t, u); if(action != null) action(t, u); return u; } // , Action action - public static T ToT(this U u, GenericMapper mapper, Func func,Action? action = null) where U :class where T :class + public static T ToT(this U u, GenericMapper mapper, Func func,Action? action = null,bool useMapper = true) where U :class where T :class { var result = mapper.GetT(u); if(result != null) return result; T t = func(u); - mapper.Add(t, u); + if(useMapper) mapper.Add(t, u); if(action != null) action(u, t); return t; diff --git a/src/Shared/ModelException.cs b/src/Shared/ModelException.cs new file mode 100644 index 0000000..4b074f0 --- /dev/null +++ b/src/Shared/ModelException.cs @@ -0,0 +1,15 @@ +namespace Shared; + +public class FriendShipException : ModelNotFoundException +{ + public FriendShipException(string message) : base(message) + { + } +} + +public class ModelNotFoundException : Exception +{ + public ModelNotFoundException(string message) : base(message) + { + } +} diff --git a/src/StubAPI/ActivityService.cs b/src/StubAPI/ActivityService.cs index fe08608..334e320 100644 --- a/src/StubAPI/ActivityService.cs +++ b/src/StubAPI/ActivityService.cs @@ -1,3 +1,5 @@ +using Dto; +using Dto.Tiny; using Model; using Model.Repository; using Shared; @@ -6,17 +8,17 @@ namespace StubAPI; public class ActivityService: IActivityRepository { - private List _activities = new List( - new Activity[] + private List _activities = new List( + new ActivityTinyDto[] { - new Activity + new ActivityTinyDto { 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, + EffortFelt = 3, Variability = 0.5f, Variance = 0.5f, StandardDeviation = 0.5f, @@ -25,33 +27,46 @@ public class ActivityService: IActivityRepository Minimum = 0, AverageTemperature = 20.0f, HasAutoPause = false, - Athlete = new User - { - Id = 3, Username = "Athlete3", ProfilePicture = "https://plus.unsplash.com/premium_photo-1705091981693-6006f8a20479?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", FirstName = "First3", LastName = "Last3", - Sexe = "M", Lenght = 190, Weight = 80, DateOfBirth = new DateTime(1994, 3, 3), Email = "ath@ex.fr", - Role = new Athlete() - } }, } ); - public async Task?> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false) - => await Task.FromResult(_activities.GetItemsWithFilterAndOrdering(c=>true,index, count,criteria != ActivityOrderCriteria.None ? criteria: null , descending)); + public async Task?> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false) + => await Task.FromResult(_activities.GetItemsWithFilterAndOrdering(a => true, index, count, criteria, descending)); public Task GetActivityByIdAsync(int id) { - return Task.FromResult(_activities.FirstOrDefault(s => s.Id == id)); + throw new NotImplementedException(); + // return Task.FromResult(_activities.FirstOrDefault(s => s.Id == id)?.ToModel()); + } + + public async Task GetActivityById(int id) + { + throw new NotImplementedException(); } public Task AddActivity(Activity activity) - => _activities.AddItem(activity); - + => throw new NotImplementedException(); + + + public async Task AddActivity(NewActivityDto activity) + { + throw new NotImplementedException(); + } + + public async Task UpdateActivity(int id, ActivityTinyDto activity) + { + throw new NotImplementedException(); + } + public async Task UpdateActivity(int id, Activity activity) { - var oldActivity = _activities.FirstOrDefault(s => s.Id == id); + throw new NotImplementedException(); + + /*var oldActivity = _activities.FirstOrDefault(s => s.Id == id); if (oldActivity == null) return null; - return await _activities.UpdateItem(oldActivity, activity); + return await _activities.UpdateItem(oldActivity, activity);*/ } public Task DeleteActivity(int id) @@ -66,13 +81,16 @@ public class ActivityService: IActivityRepository public async Task?> GetActivitiesByUser(int userId, int index, int count, ActivityOrderCriteria criteria, bool descending = false) { - var activities = _activities.GetItemsWithFilterAndOrdering(a => a.Athlete.Id == userId, index, count, + throw new NotImplementedException(); + + /* var activities = _activities.GetItemsWithFilterAndOrdering(a => a.Athlete.Id == userId, index, count, criteria != ActivityOrderCriteria.None ? criteria : null, descending); - return await Task.FromResult(activities); + return await Task.FromResult(activities);*/ } public Task GetNbActivitiesByUser(int userId) { - return Task.FromResult(_activities.Count(a => a.Athlete.Id == userId)); + throw new NotImplementedException(); + // return Task.FromResult(_activities.Count(a => a.Athlete.Id == userId)); } } \ No newline at end of file diff --git a/src/StubAPI/AthleteService.cs b/src/StubAPI/AthleteService.cs index 4d8bf29..2416b66 100644 --- a/src/StubAPI/AthleteService.cs +++ b/src/StubAPI/AthleteService.cs @@ -1,4 +1,6 @@ -using Model; +using Dto; +using Dto.Tiny; +using Model; using Model.Repository; using Shared; @@ -11,21 +13,21 @@ public class UserService : IUserRepository new User { Id = 1, Username = "DoeDoe", ProfilePicture = "https://images.unsplash.com/photo-1682687982134-2ac563b2228b?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", FirstName = "John", LastName = "Doe", - Sexe = "M", Lenght = 180, Weight = 70, DateOfBirth = new DateTime(1990, 1, 1), + Sexe = 'M', Lenght = 180, Weight = 70, DateOfBirth = new DateTime(1990, 1, 1), Email = "john.doe@example.com", Role = new Athlete() }, new User { Id = 2, Username = "SmithSmith", ProfilePicture = "https://images.unsplash.com/photo-1709507779917-242b560288be?q=80&w=2080&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", FirstName = "Jane", LastName = "Smith", - Sexe = "F", Lenght = 170, Weight = 60, DateOfBirth = new DateTime(1992, 2, 2), + Sexe = 'F', Lenght = 170, Weight = 60, DateOfBirth = new DateTime(1992, 2, 2), Email = "athlete2@example.com", Role = new Coach() }, new User { Id = 3, Username = "Athlete3", ProfilePicture = "https://plus.unsplash.com/premium_photo-1705091981693-6006f8a20479?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", FirstName = "First3", LastName = "Last3", - Sexe = "M", Lenght = 190, Weight = 80, DateOfBirth = new DateTime(1994, 3, 3), Email = "ath@ex.fr", + Sexe = 'M', Lenght = 190, Weight = 80, DateOfBirth = new DateTime(1994, 3, 3), Email = "ath@ex.fr", Role = new Athlete() } @@ -34,6 +36,26 @@ public class UserService : IUserRepository public async Task> GetUsers(int index, int count, AthleteOrderCriteria? orderingProperty = null, bool descending = false) => athletes.GetItemsWithFilterAndOrdering(c=>true,index, count,orderingProperty != AthleteOrderCriteria.None ? orderingProperty: null , descending); + public async Task?> GetUsersTiny(int index, int count, AthleteOrderCriteria? criteria, bool descending = false) + { + throw new NotImplementedException(); + } + + public async Task AddFollowing(int fromUser, int toUser) + { + throw new NotImplementedException(); + } + + public async Task RemoveFollowing(int fromUser, int toUser) + { + throw new NotImplementedException(); + } + + public async Task?> GetFriends(int user, int index, int count, AthleteOrderCriteria? criteria, bool descending = false) + { + throw new NotImplementedException(); + } + public async Task AddFriend(User user, User friend) { if (user == null || friend == null) @@ -71,9 +93,24 @@ public class UserService : IUserRepository public async Task?>? GetFriends(User user, int index, int count, AthleteOrderCriteria? criteria, bool descending = false) =>await Task.FromResult(athletes.FirstOrDefault(s => s.Id == user.Id)?.Users.GetItemsWithFilterAndOrdering(c=>true,index, count,criteria, descending)); - public Task GetNbFriends(User user) + public Task GetNbFriends(int user) + { + return Task.FromResult(athletes.FirstOrDefault(s => s.Id == user)?.Users.Count ?? 0); + } + + public async Task UpdateUser(int old, UserTinyDto user) + { + throw new NotImplementedException(); + } + + public async Task GetUserById(int id) + { + throw new NotImplementedException(); + } + + public Task GetUserTinyById(int id) { - return Task.FromResult(athletes.FirstOrDefault(s => s.Id == user.Id)?.Users.Count ?? 0); + throw new NotImplementedException(); } public async Task> GetItems(int index, int count, string? orderingProperty = null, diff --git a/src/StubAPI/StubAPI.csproj b/src/StubAPI/StubAPI.csproj index eea9fb7..85860b7 100644 --- a/src/StubAPI/StubAPI.csproj +++ b/src/StubAPI/StubAPI.csproj @@ -7,6 +7,7 @@ + diff --git a/src/StubAPI/StubData.cs b/src/StubAPI/StubData.cs index bceb496..f21772c 100644 --- a/src/StubAPI/StubData.cs +++ b/src/StubAPI/StubData.cs @@ -1,3 +1,4 @@ +using Dto.Tiny; using Model.Manager; using Model.Repository; @@ -7,6 +8,7 @@ public class StubData : IDataManager { public IUserRepository UserRepo { get; } public IActivityRepository ActivityRepo { get; } + public IDataSourceRepository DataSourceRepo { get; } public StubData() { diff --git a/src/StubApi/AthleteStubDto.cs b/src/StubApi/AthleteStubDto.cs deleted file mode 100644 index 5725d31..0000000 --- a/src/StubApi/AthleteStubDto.cs +++ /dev/null @@ -1,4 +0,0 @@ -// using Shared; - -// namespace - diff --git a/src/StubbedContextLib/AthleteStubbedContext.cs b/src/StubbedContextLib/AthleteStubbedContext.cs index f45140f..2644757 100644 --- a/src/StubbedContextLib/AthleteStubbedContext.cs +++ b/src/StubbedContextLib/AthleteStubbedContext.cs @@ -42,11 +42,11 @@ namespace StubbedContextLib modelBuilder.Entity().HasData(picture); modelBuilder.Entity().HasData( - new AthleteEntity { IdAthlete = 1, Username = "Doe",ProfilPicture = picture2, ImageId = Guid.Parse("{8d121cdc-6787-4738-8edd-9e026ac16b65}") ,LastName = "Doe", FirstName = "John", Email = "john.doe@example.com", Password = "password123", Sexe = "M", Length = 1.80, Weight = 75, DateOfBirth = new DateOnly(1990, 01, 01), IsCoach = true , DataSourceId = 1}, - new AthleteEntity { IdAthlete = 2, Username = "Smith",ProfilPicture = picture2,ImageId = Guid.Parse("{8d121cdc-6787-4738-8edd-9e026ac16b65}") ,LastName = "Smith", FirstName = "Jane", Email = "jane.smith@exemple.com", Password = "secure456", Sexe = "F", Length = 1.65, Weight = 60, DateOfBirth = new DateOnly(1995, 01, 01), IsCoach = false, DataSourceId = 1 }, - new AthleteEntity { IdAthlete = 3, Username = "Martin",ProfilPicture = picture2,ImageId = Guid.Parse("{8d121cdc-6787-4738-8edd-9e026ac16b65}") ,LastName = "Martin", FirstName = "Paul", Email = "paul.martin@example.com", Password = "super789", Sexe = "M", Length = 1.75, Weight = 68, DateOfBirth = new DateOnly(1992, 01, 01), IsCoach = true, DataSourceId = 1}, - new AthleteEntity { IdAthlete = 4, Username = "Brown",ProfilPicture = picture2, ImageId = Guid.Parse("{8d121cdc-6787-4738-8edd-9e026ac16b65}"),LastName = "Brown", FirstName = "Anna", Email = "anna.brown@example.com", Password = "test000", Sexe = "F", Length = 1.70, Weight = 58, DateOfBirth = new DateOnly(1993, 01, 01), IsCoach = false, DataSourceId = 2 }, - new AthleteEntity { IdAthlete = 5, Username = "Lee", ProfilPicture = picture2, ImageId = Guid.Parse("{8d121cdc-6787-4738-8edd-9e026ac16b65}"),LastName = "Lee", FirstName = "Bruce", Email = "bruce.lee@example.com", Password = "hello321", Sexe = "M", Length = 2.0, Weight = 90, DateOfBirth = new DateOnly(1991, 01, 01), IsCoach = false, DataSourceId = 3 } + new AthleteEntity { Id = 1, UserName = "Doe",ProfilPicture = picture2, ImageId = Guid.Parse("{8d121cdc-6787-4738-8edd-9e026ac16b65}") ,LastName = "Doe", FirstName = "John", Email = "john.doe@example.com", PasswordHash = "password123", Sexe = 'M', Length = 1.80, Weight = 75, DateOfBirth = new DateOnly(1990, 01, 01), IsCoach = true , DataSourceId = 1}, + new AthleteEntity { Id = 2, UserName = "Smith",ProfilPicture = picture2,LastName = "Smith", FirstName = "Jane", Email = "jane.smith@exemple.com", PasswordHash = "secure456", Sexe = 'F', Length = 1.65, Weight = 60, DateOfBirth = new DateOnly(1995, 01, 01), IsCoach = false, DataSourceId = 1 }, + new AthleteEntity { Id = 3, UserName = "Martin",ProfilPicture = picture2,ImageId = Guid.Parse("{8d121cdc-6787-4738-8edd-9e026ac16b65}") ,LastName = "Martin", FirstName = "Paul", Email = "paul.martin@example.com", PasswordHash = "super789", Sexe = 'M', Length = 1.75, Weight = 68, DateOfBirth = new DateOnly(1992, 01, 01), IsCoach = true, DataSourceId = 1}, + new AthleteEntity { Id = 4, UserName = "Brown",ProfilPicture = picture2, ImageId = Guid.Parse("{8d121cdc-6787-4738-8edd-9e026ac16b65}"),LastName = "Brown", FirstName = "Anna", Email = "anna.brown@example.com", PasswordHash = "test000", Sexe = 'F', Length = 1.70, Weight = 58, DateOfBirth = new DateOnly(1993, 01, 01), IsCoach = false, DataSourceId = 2 }, + new AthleteEntity { Id = 5, UserName = "Lee", ProfilPicture = picture2, ImageId = Guid.Parse("{8d121cdc-6787-4738-8edd-9e026ac16b65}"),LastName = "Lee", FirstName = "Bruce", Email = "bruce.lee@example.com", PasswordHash = "hello321", Sexe = 'M', Length = 2.0, Weight = 90, DateOfBirth = new DateOnly(1991, 01, 01), IsCoach = false, DataSourceId = 3 } ); } } diff --git a/src/StubbedContextLib/FriendshipStubbedContext.cs b/src/StubbedContextLib/FriendshipStubbedContext.cs index 70eb76f..5631ca1 100644 --- a/src/StubbedContextLib/FriendshipStubbedContext.cs +++ b/src/StubbedContextLib/FriendshipStubbedContext.cs @@ -39,10 +39,7 @@ namespace StubbedContextLib modelBuilder.Entity().HasData( new FriendshipEntity { FollowerId = 1, FollowingId = 2 }, new FriendshipEntity { FollowerId = 1, FollowingId = 3 }, - new FriendshipEntity { FollowerId = 1, FollowingId = 4 }, - new FriendshipEntity { FollowerId = 1, FollowingId = 5 }, - new FriendshipEntity { FollowerId = 2, FollowingId = 1 }, - new FriendshipEntity { FollowerId = 2, FollowingId = 3 } + new FriendshipEntity { FollowerId = 3, FollowingId = 1 } ); } } diff --git a/src/Tests/ConsoleTestEFMapper/Program.cs b/src/Tests/ConsoleTestEFMapper/Program.cs index bc71893..c1fb1ff 100644 --- a/src/Tests/ConsoleTestEFMapper/Program.cs +++ b/src/Tests/ConsoleTestEFMapper/Program.cs @@ -8,20 +8,19 @@ using Shared; using StubbedContextLib; using static Model2Entities.DbDataManager; -namespace Model2Entities -{ +namespace ConsoleTestEFMapper; + + static class Program { + static async Task Main(string[] args) { // Instanciation de DbDataManager et ActivityRepository - var dataManager = new DbDataManager(new TrainingStubbedContext()); - var logger = new Logger(new LoggerFactory()); - - // Test de la méthode GetActivities - await ActivitiesTestAsync(dataManager, logger); + Console.WriteLine(""); } + /* static async Task ActivitiesTestAsync(DbDataManager dataManager, ILogger logger) { var activityRepository = new ActivityRepository(dataManager, logger); @@ -48,7 +47,7 @@ namespace Model2Entities } Console.WriteLine(); - // // Test de la méthode AddActivity + // Test de la méthode AddActivity Console.WriteLine("Testing AddActivity method..."); var user = new User { @@ -113,6 +112,5 @@ namespace Model2Entities Console.WriteLine($"Total number of activities: {itemCount}"); } static void UsersTest() - {} + {}*/ } -} \ No newline at end of file diff --git a/src/Tests/ConsoleTestEntities/Program.cs b/src/Tests/ConsoleTestEntities/Program.cs index 826f345..12ac022 100644 --- a/src/Tests/ConsoleTestEntities/Program.cs +++ b/src/Tests/ConsoleTestEntities/Program.cs @@ -56,34 +56,34 @@ class Program foreach (var athlete in db.AthletesSet) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); Console.WriteLine("Accès à l'athlete d'id '2' :"); Console.WriteLine("---------------------------------"); - foreach (var athlete in db.AthletesSet.Where(a => a.IdAthlete == 2)) + foreach (var athlete in db.AthletesSet.Where(a => a.Id == 2)) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); Console.WriteLine("Accès à l'athlete de username 'Doe' :"); Console.WriteLine("---------------------------------"); - foreach (var athlete in db.AthletesSet.Where(a => a.Username == "Doe")) + foreach (var athlete in db.AthletesSet.Where(a => a.UserName == "Doe")) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); Console.WriteLine("Accès à l'athlete de sexe 'F' :"); Console.WriteLine("---------------------------------"); - foreach (var athlete in db.AthletesSet.Where(a => a.Sexe == "F")) + foreach (var athlete in db.AthletesSet.Where(a => a.Sexe == 'F')) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); @@ -92,7 +92,7 @@ class Program Console.WriteLine("---------------------------------"); foreach (var athlete in db.AthletesSet.Where(a => a.Email == "bruce.lee@example.com")) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); @@ -101,7 +101,7 @@ class Program Console.WriteLine("---------------------------------"); foreach (var athlete in db.AthletesSet.Where(a => a.Weight == 90)) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); @@ -110,7 +110,7 @@ class Program Console.WriteLine("---------------------------------"); foreach (var athlete in db.AthletesSet.Where(a => a.Length == 1.80)) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); @@ -119,7 +119,7 @@ class Program Console.WriteLine("---------------------------------"); foreach (var athlete in db.AthletesSet.Where(a => a.DateOfBirth == new DateOnly(1990, 01, 01))) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); @@ -128,7 +128,7 @@ class Program Console.WriteLine("---------------------------------"); foreach (var athlete in db.AthletesSet.Where(a => a.LastName == "Martin")) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); @@ -137,7 +137,7 @@ class Program Console.WriteLine("---------------------------------"); foreach (var athlete in db.AthletesSet.Where(a => a.FirstName == "Anna")) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); @@ -146,7 +146,7 @@ class Program Console.WriteLine("---------------------------------"); foreach (var athlete in db.AthletesSet.Where(a => a.LastName == "Brown" && a.FirstName == "Anna")) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); @@ -155,7 +155,7 @@ class Program Console.WriteLine("---------------------------------"); foreach (var athlete in db.AthletesSet.Where(a => a.IsCoach == true)) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } Console.WriteLine("---------------------------------\n"); @@ -755,7 +755,7 @@ class Program Console.WriteLine("Test d'ajout, de modification et de suppression des athletes :"); var picture = "https://davidalmeida.site/assets/me_avatar.f77af006.png"; // Ajout d'un nouveau livre - var newAthlete = new AthleteEntity { Username = "Doe", LastName = "Doe",ProfilPicture = picture,FirstName = "John", Email = "essaie.example.com", Password = "TheNewPassword", Sexe = "M", Length = 1.80, Weight = 90, DateOfBirth = new DateOnly(2024, 02, 22), IsCoach = false }; + var newAthlete = new AthleteEntity { UserName = "Doe", LastName = "Doe",ProfilPicture = picture,FirstName = "John", Email = "essaie.example.com", PasswordHash = "TheNewPassword", Sexe = 'M', Length = 1.80, Weight = 90, DateOfBirth = new DateOnly(2024, 02, 22), IsCoach = false }; db.AthletesSet.Add(newAthlete); db.SaveChanges(); @@ -763,7 +763,7 @@ class Program Console.WriteLine("Athlete après ajout :"); foreach (var athlete in db.AthletesSet) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } // Modification du titre du nouveau livre @@ -774,7 +774,7 @@ class Program Console.WriteLine("Livres après modification :"); foreach (var athlete in db.AthletesSet) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } // Suppression du nouveau livre @@ -785,7 +785,7 @@ class Program Console.WriteLine("Livres après suppression :"); foreach (var athlete in db.AthletesSet) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.Username}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.UserName}, {athlete.LastName}, {athlete.FirstName}, {athlete.Email}, {athlete.Sexe}, {athlete.Length}, {athlete.Weight}, {athlete.DateOfBirth}, {athlete.IsCoach}"); } } diff --git a/src/Tests/ConsoleTestRelationships/Program.cs b/src/Tests/ConsoleTestRelationships/Program.cs index fa9797e..5ff197e 100644 --- a/src/Tests/ConsoleTestRelationships/Program.cs +++ b/src/Tests/ConsoleTestRelationships/Program.cs @@ -96,7 +96,7 @@ class Program foreach (var athlete in dataSource.Athletes) { - Console.WriteLine($"\t\t\t{athlete.IdAthlete} - {athlete.FirstName}, {athlete.LastName}, {athlete.DateOfBirth}, {athlete.Sexe}, {athlete.Weight}, {athlete.IsCoach}"); + Console.WriteLine($"\t\t\t{athlete.Id} - {athlete.FirstName}, {athlete.LastName}, {athlete.DateOfBirth}, {athlete.Sexe}, {athlete.Weight}, {athlete.IsCoach}"); } } @@ -122,7 +122,7 @@ class Program foreach (var athlete in dataSource.Athletes) { - Console.WriteLine($"\t\t\t{athlete.IdAthlete} - {athlete.FirstName}, {athlete.LastName}, {athlete.DateOfBirth}, {athlete.Sexe}, {athlete.Weight}, {athlete.IsCoach}"); + Console.WriteLine($"\t\t\t{athlete.Id} - {athlete.FirstName}, {athlete.LastName}, {athlete.DateOfBirth}, {athlete.Sexe}, {athlete.Weight}, {athlete.IsCoach}"); } } @@ -138,7 +138,7 @@ class Program foreach (var athlete in db.AthletesSet.Include(a => a.Statistics).Include(a => a.Activities).Include(a => a.TrainingsAthlete).Include(a => a.NotificationsSent).Include(a => a.DataSource).Include(a => a.TrainingsCoach).Include(a => a.NotificationsReceived)) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.FirstName}, {athlete.LastName}, {athlete.DateOfBirth}, {athlete.Sexe}, {athlete.Weight}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.FirstName}, {athlete.LastName}, {athlete.DateOfBirth}, {athlete.Sexe}, {athlete.Weight}, {athlete.IsCoach}"); Console.WriteLine("\t\tStatistiques :"); Console.WriteLine("\t\t---------------------------------"); @@ -199,9 +199,9 @@ class Program Console.WriteLine("Accès à l'athlète d'id '2' :"); Console.WriteLine("---------------------------------"); - foreach (var athlete in db.AthletesSet.Where(a => a.IdAthlete == 2).Include(a => a.Statistics).Include(a => a.Activities).Include(a => a.TrainingsAthlete).Include(a => a.NotificationsSent).Include(a => a.DataSource).Include(a => a.TrainingsCoach).Include(a => a.NotificationsReceived)) + foreach (var athlete in db.AthletesSet.Where(a => a.Id == 2).Include(a => a.Statistics).Include(a => a.Activities).Include(a => a.TrainingsAthlete).Include(a => a.NotificationsSent).Include(a => a.DataSource).Include(a => a.TrainingsCoach).Include(a => a.NotificationsReceived)) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.FirstName}, {athlete.LastName}, {athlete.DateOfBirth}, {athlete.Sexe}, {athlete.Weight}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.FirstName}, {athlete.LastName}, {athlete.DateOfBirth}, {athlete.Sexe}, {athlete.Weight}, {athlete.IsCoach}"); Console.WriteLine("\t\tStatistiques :"); Console.WriteLine("\t\t---------------------------------"); @@ -269,7 +269,7 @@ class Program foreach (var athlete in db.AthletesSet.Include(f => f.Followers).Include(f => f.Followings)) { - Console.WriteLine($"\t{athlete.IdAthlete} - {athlete.FirstName}, {athlete.LastName}, {athlete.DateOfBirth}, {athlete.Sexe}, {athlete.Weight}, {athlete.IsCoach}"); + Console.WriteLine($"\t{athlete.Id} - {athlete.FirstName}, {athlete.LastName}, {athlete.DateOfBirth}, {athlete.Sexe}, {athlete.Weight}, {athlete.IsCoach}"); Console.WriteLine($""); Console.WriteLine($""); diff --git a/src/Tests/RepositoriesUnitTest/ActivityRepository.cs b/src/Tests/RepositoriesUnitTest/ActivityRepository.cs new file mode 100644 index 0000000..a366bfb --- /dev/null +++ b/src/Tests/RepositoriesUnitTest/ActivityRepository.cs @@ -0,0 +1,114 @@ +using Xunit; +using Model2Entities; +using Microsoft.EntityFrameworkCore; +using DbContextLib; +using StubbedContextLib; +using System.Linq; +using Microsoft.Data.Sqlite; +using System; +using EFMappers; +using Shared; +using Model; +using Moq; +using Microsoft.Extensions.Logging; +using Entities; +/* +namespace UnitTestsEntities +{ + public class ActivityRepositoryTests : IClassFixture + { + private readonly DatabaseFixture _fixture; + + public ActivityRepositoryTests(DatabaseFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task GetActivities_ReturnsActivities() + { + var options = new DbContextOptionsBuilder() + .UseSqlite(_fixture._connection) + .Options; + + using (var context = new HeartTrackContext(options)) + { + context.Database.EnsureCreated(); + } + + using (var context = new HeartTrackContext(options)) + { + var repository = new DbDataManager.ActivityRepository(new DbDataManager(context), null); + var activities = await repository.GetActivities(0, 10, ActivityOrderCriteria.None); + + Assert.NotNull(activities); + Assert.Equal(10, activities.Count()); + } + } + [Fact] + public async Task GetActivityByIdAsync_ReturnsCorrectActivity_WhenIdExists() + { + // Arrange + var activityId = 1; + var expectedActivity = new Activity { Id = activityId, Type = "Running" }; + + var mockDataManager = new Mock(); + mockDataManager.Setup(dm => dm.DbContext.ActivitiesSet.SingleOrDefaultAsync(a => a.IdActivity == activityId)) + .ReturnsAsync(expectedActivity.ToEntity()); + + var loggerMock = new Mock>(); + var activityRepository = new DbDataManager.ActivityRepository(mockDataManager.Object, loggerMock.Object); + + // Act + var result = await activityRepository.GetActivityByIdAsync(activityId); + + // Assert + Assert.NotNull(result); + Assert.Equal(expectedActivity.Id, result.Id); + Assert.Equal(expectedActivity.Type, result.Type); + } + + [Fact] + public async Task GetActivityByIdAsync_ReturnsNull_WhenIdDoesNotExist() + { + // Arrange + var activityId = 999; + + var mockDataManager = new Mock(); + mockDataManager.Setup(dm => dm.DbContext.ActivitiesSet.SingleOrDefaultAsync(a => a.IdActivity == activityId)) + .ReturnsAsync((ActivityEntity)null); + + var loggerMock = new Mock>(); + var activityRepository = new DbDataManager.ActivityRepository(mockDataManager.Object, loggerMock.Object); + + // Act + var result = await activityRepository.GetActivityByIdAsync(activityId); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task AddActivity_SuccessfullyAddsNewActivity() + { + // Arrange + var newActivity = new Activity { Type = "Walking" }; + + var mockDataManager = new Mock(); + mockDataManager.Setup(dm => dm.DbContext.AddItem(It.IsAny())) + .ReturnsAsync(newActivity.ToEntity()); + + var loggerMock = new Mock>(); + var activityRepository = new DbDataManager.ActivityRepository(mockDataManager.Object, loggerMock.Object); + + // Act + var result = await activityRepository.AddActivity(newActivity); + + // Assert + Assert.NotNull(result); + Assert.Equal(newActivity.Type, result.Type); + } + + } +} +*/ diff --git a/src/Tests/RepositoriesUnitTest/GlobalUsings.cs b/src/Tests/RepositoriesUnitTest/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/src/Tests/RepositoriesUnitTest/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/src/Tests/RepositoriesUnitTest/RepositoriesUnitTest.csproj b/src/Tests/RepositoriesUnitTest/RepositoriesUnitTest.csproj new file mode 100644 index 0000000..417f28f --- /dev/null +++ b/src/Tests/RepositoriesUnitTest/RepositoriesUnitTest.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/src/Tests/RepositoriesUnitTest/UnitTest1.cs b/src/Tests/RepositoriesUnitTest/UnitTest1.cs new file mode 100644 index 0000000..392318c --- /dev/null +++ b/src/Tests/RepositoriesUnitTest/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace RepositoriesUnitTest; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} \ No newline at end of file diff --git a/src/Tests/TestApi/GlobalUsings.cs b/src/Tests/TestApi/GlobalUsings.cs deleted file mode 100644 index ab67c7e..0000000 --- a/src/Tests/TestApi/GlobalUsings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/src/Tests/TestApi/TestApi.csproj b/src/Tests/TestApi/TestApi.csproj deleted file mode 100644 index 719074b..0000000 --- a/src/Tests/TestApi/TestApi.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - - - - - - - - - - - diff --git a/src/Tests/TestApi/UserControllerTest.cs b/src/Tests/TestApi/UserControllerTest.cs deleted file mode 100644 index e69de29..0000000 diff --git a/src/Tests/TestStubEF/Program.cs b/src/Tests/TestStubEF/Program.cs index 83fa4f4..3751555 100644 --- a/src/Tests/TestStubEF/Program.cs +++ b/src/Tests/TestStubEF/Program.cs @@ -1,2 +1,2 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/src/Tests/TestStubEF/TestStubEF.csproj b/src/Tests/TestStubEF/TestStubEF.csproj index 206b89a..2150e37 100644 --- a/src/Tests/TestStubEF/TestStubEF.csproj +++ b/src/Tests/TestStubEF/TestStubEF.csproj @@ -1,10 +1,10 @@ - - - - Exe - net8.0 - enable - enable - - - + + + + Exe + net8.0 + enable + enable + + + diff --git a/src/Tests/TestsAPI/ClientTests/ClientTests.csproj b/src/Tests/TestsAPI/ClientTests/ClientTests.csproj deleted file mode 100644 index 206b89a..0000000 --- a/src/Tests/TestsAPI/ClientTests/ClientTests.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - diff --git a/src/Tests/TestsAPI/ClientTests/HttpClientManager.cs b/src/Tests/TestsAPI/ClientTests/HttpClientManager.cs deleted file mode 100644 index 2e8e593..0000000 --- a/src/Tests/TestsAPI/ClientTests/HttpClientManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace ClientTests; - -public class HttpClientManager -{ - protected readonly HttpClient _httpClient; - - public HttpClientManager(HttpClient httpClient) - { - _httpClient = httpClient; - _httpClient.BaseAddress = new Uri("https://localhost:7252"); - } -} diff --git a/src/Tests/TestsAPI/ClientTests/Program.cs b/src/Tests/TestsAPI/ClientTests/Program.cs deleted file mode 100644 index 83fa4f4..0000000 --- a/src/Tests/TestsAPI/ClientTests/Program.cs +++ /dev/null @@ -1,2 +0,0 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); diff --git a/src/Tests/TestsAPI/UnitTestApi/Controllers/UsersControllerTest.cs b/src/Tests/TestsAPI/UnitTestApi/Controllers/UsersControllerTest.cs index 0a5eb52..5896ea3 100644 --- a/src/Tests/TestsAPI/UnitTestApi/Controllers/UsersControllerTest.cs +++ b/src/Tests/TestsAPI/UnitTestApi/Controllers/UsersControllerTest.cs @@ -1,5 +1,6 @@ using APIMappers; using Dto; +using Dto.Tiny; using HeartTrackAPI.Controllers; using HeartTrackAPI.Request; using HeartTrackAPI.Responce; @@ -31,7 +32,7 @@ public class UsersControllerTest ProfilePicture = "https://images.unsplash.com/photo-1682687982134-2ac563b2228b?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", FirstName = "John", LastName = "Doe", - Sexe = "M", Lenght = 180, Weight = 70, DateOfBirth = new DateTime(1990, 1, 1), + Sexe = 'M', Lenght = 180, Weight = 70, DateOfBirth = new DateTime(1990, 1, 1), Email = "john.doe@example.com", Role = new Athlete() }, @@ -41,7 +42,7 @@ public class UsersControllerTest ProfilePicture = "https://images.unsplash.com/photo-1709507779917-242b560288be?q=80&w=2080&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", FirstName = "Jane", LastName = "Smith", - Sexe = "F", Lenght = 170, Weight = 60, DateOfBirth = new DateTime(1992, 2, 2), + Sexe = 'F', Lenght = 170, Weight = 60, DateOfBirth = new DateTime(1992, 2, 2), Email = "athlete2@example.com", Role = new Coach() }, @@ -51,7 +52,7 @@ public class UsersControllerTest 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", + Sexe = 'M', Lenght = 190, Weight = 80, DateOfBirth = new DateTime(1994, 3, 3), Email = "ath@ex.fr", Role = new Athlete() } ]; @@ -75,14 +76,6 @@ public class UsersControllerTest _usersController = new UsersController(new NullLogger(), _dataManagerMock.Object); } /* - [TestInitialize] - public void SetUp() - { - _dataManager = new StubData(); - _usersController = new UsersController(new NullLogger(), _dataManager); - }*/ - - [TestMethod] public async Task Get_ReturnsPageResponse_WhenRequestIsValid() { @@ -101,9 +94,9 @@ public class UsersControllerTest var okResult = result.Result as OkObjectResult; // Assert Assert.IsNotNull(okResult); - Assert.IsInstanceOfType(okResult.Value, typeof(PageResponse)); - var pageResponse = okResult.Value as PageResponse; - Assert.IsNotNull(pageResponse); + Assert.IsInstanceOfType(okResult.Value, typeof(PageResponse)); + var pageResponse = okResult.Value as PageResponse; + Assert.IsNotNull(pageResponse); Assert.AreEqual(3, pageResponse.Items.Count()); Assert.AreEqual(3, pageResponse.Total); Assert.AreEqual(0, pageResponse.Index); @@ -132,8 +125,8 @@ public class UsersControllerTest var okResult = result.Result as OkObjectResult; // Assert Assert.IsNotNull(okResult); - Assert.IsInstanceOfType(okResult.Value, typeof(PageResponse)); - var pageResponse = okResult.Value as PageResponse; + Assert.IsInstanceOfType(okResult.Value, typeof(PageResponse)); + var pageResponse = okResult.Value as PageResponse; Assert.IsNotNull(pageResponse); Assert.AreEqual(expectedItemCount, pageResponse.Items.Count()); } @@ -162,7 +155,10 @@ public class UsersControllerTest { var id = 1; - _dataManagerMock.Setup(dm => dm.UserRepo.GetItemById(id)).ReturnsAsync(_users.First(x => x.Id == id)); + _dataManagerMock.Setup(dm => dm.UserRepo.GetUserById(id)).ReturnsAsync(List() + { + + }); // Act var result = await _usersController.GetById(id) ; @@ -173,12 +169,12 @@ public class UsersControllerTest Assert.IsNotNull(okResult); var resultObject = result.Result as OkObjectResult; Assert.IsNotNull(resultObject); - Assert.IsInstanceOfType(resultObject.Value, typeof(UserDto)); - var user = resultObject.Value as UserDto; + Assert.IsInstanceOfType(resultObject.Value, typeof(ResponseUserDto)); + var user = resultObject.Value as ResponseUserDto; Assert.IsNotNull(user); var tmp = _users.First(x => x.Id == id).ToDto(); Assert.AreEqual(tmp.Id, user.Id); - } + }*/ [TestMethod] public async Task GetById_ReturnsUserDto_WhenRequestUserDoesNotExist() diff --git a/src/Tests/UnitTestsEntities/AthleteEntityTests.cs b/src/Tests/UnitTestsEntities/AthleteEntityTests.cs index 1f67d03..81a6d6b 100644 --- a/src/Tests/UnitTestsEntities/AthleteEntityTests.cs +++ b/src/Tests/UnitTestsEntities/AthleteEntityTests.cs @@ -12,14 +12,14 @@ public class AthleteEntityTests (DatabaseFixture fixture) : IClassFixture a.Username == "john_doe"); + var savedAthlete = context.AthletesSet.First(a => a.UserName == "john_doe"); Assert.NotNull(savedAthlete); - Assert.Equal("john_doe", savedAthlete.Username); + Assert.Equal("john_doe", savedAthlete.UserName); } } @@ -47,14 +47,14 @@ public class AthleteEntityTests (DatabaseFixture fixture) : IClassFixture a.Username == "jane_smith"); - savedAthlete.Username = "jane_doe"; + var savedAthlete = context.AthletesSet.First(a => a.UserName == "jane_smith"); + savedAthlete.UserName = "jane_doe"; context.SaveChanges(); } using (var context = new TrainingStubbedContext(fixture._options)) { - var updatedAthlete = context.AthletesSet.First(a => a.Username == "jane_doe"); + var updatedAthlete = context.AthletesSet.First(a => a.UserName == "jane_doe"); Assert.NotNull(updatedAthlete); - Assert.Equal("jane_doe", updatedAthlete.Username); + Assert.Equal("jane_doe", updatedAthlete.UserName); Assert.Equal("Smith", updatedAthlete.LastName); } } @@ -89,14 +89,14 @@ public class AthleteEntityTests (DatabaseFixture fixture) : IClassFixture a.Username == "test_user"); + var savedAthlete = context.AthletesSet.First(a => a.UserName == "test_user"); context.AthletesSet.Remove(savedAthlete); context.SaveChanges(); } using (var context = new TrainingStubbedContext(fixture._options)) { - var deletedAthlete = context.AthletesSet.FirstOrDefault(a => a.Username == "test_user"); + var deletedAthlete = context.AthletesSet.FirstOrDefault(a => a.UserName == "test_user"); Assert.Null(deletedAthlete); } } @@ -128,14 +128,14 @@ public class AthleteEntityTests (DatabaseFixture fixture) : IClassFixture a.Username == "john_doe"); + var savedAthlete = context.AthletesSet.First(a => a.UserName == "john_doe"); Assert.NotNull(savedAthlete); - Assert.Equal("john_doe", savedAthlete.Username); + Assert.Equal("john_doe", savedAthlete.UserName); Assert.Equal("Doe", savedAthlete.LastName); Assert.Equal("John", savedAthlete.FirstName); Assert.Equal("john.doe@example.com", savedAthlete.Email); - Assert.Equal("M", savedAthlete.Sexe); + Assert.Equal('M', savedAthlete.Sexe); Assert.Equal(180.0, savedAthlete.Length); Assert.Equal(75.5f, savedAthlete.Weight); - Assert.Equal("password", savedAthlete.Password); + Assert.Equal("password", savedAthlete.PasswordHash); Assert.Equal(new DateOnly(1990, 1, 1), savedAthlete.DateOfBirth); Assert.False(savedAthlete.IsCoach); } @@ -170,14 +170,14 @@ public class AthleteEntityTests (DatabaseFixture fixture) : IClassFixture _options; public DatabaseFixture() { diff --git a/src/Tests/UnitTestsEntities/FriendshipEntityTests.cs b/src/Tests/UnitTestsEntities/FriendshipEntityTests.cs index 10cfc00..ef95bb7 100644 --- a/src/Tests/UnitTestsEntities/FriendshipEntityTests.cs +++ b/src/Tests/UnitTestsEntities/FriendshipEntityTests.cs @@ -12,14 +12,14 @@ public class FriendshipEntityTests (DatabaseFixture fixture) : IClassFixture a.Followers).Include(a => a.Followings).FirstOrDefault(a => a.Username == "follower_user1").Followers.FirstOrDefault(f => f.FollowerId == follower.IdAthlete && f.FollowingId == following.IdAthlete); + var savedFriendship = context.AthletesSet.Include(a => a.Followers).Include(a => a.Followings).FirstOrDefault(a => a.UserName == "follower_user1").Followings.FirstOrDefault(f => f.FollowerId == follower.Id && f.FollowingId == following.Id); Assert.NotNull(savedFriendship); if (savedFriendship != null) { - Assert.Equal(follower.IdAthlete, savedFriendship.FollowerId); - Assert.Equal(following.IdAthlete, savedFriendship.FollowingId); + Assert.Equal(follower.Id, savedFriendship.FollowerId); + Assert.Equal(following.Id, savedFriendship.FollowingId); } } } @@ -79,42 +79,42 @@ public class FriendshipEntityTests (DatabaseFixture fixture) : IClassFixture myRequest = new HttpRequest(); + + /*! + * \brief Constructeur de la classe ActivityServiceAPI. + * Initialise l'adresse de base du client HTTP. + #1# + public ActivityServiceAPI() + { + myRequest.HttpClient.BaseAddress = new Uri("http://localhost:5030/api/v1/Activity/"); + } + + /*! + * \brief Récupère toutes les Activités de manière asynchrone. + * \return Une tâche représentant l'opération asynchrone qui retourne une liste d'Activity. + #1# + public async Task?> GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending = false) + { + var activityDtos = await myRequest.GetAllAsync(); + return activityDtos?.ToModels(); + } + + /*! + * \brief Récupère les activités par index et compte de manière asynchrone. + * \param index L'index de départ pour la pagination. + * \param count Le nombre d'éléments à récupérer. + * \return Une tâche représentant l'opération asynchrone qui retourne une liste d'Activity. + #1# + public async Task> GetBooksAsync(ActivityOrderCriteria criteria, bool descending, int index, int count) + { + var activityDtos = await myRequest.GetAsync(criteria, descending, index, count); + return (List)activityDtos.ToModels(); + } + + /*! + * \brief Récupère une activité par son identifiant de manière asynchrone. + * \param id L'identifiant du livre à récupérer. + * \return Une tâche représentant l'opération asynchrone qui retourne une liste d'Activity. + #1# + async Task?> IActivityRepository.GetActivities(int index, int count, ActivityOrderCriteria criteria, bool descending) + { + throw new NotImplementedException(); + } + + public async Task GetActivityByIdAsync(int id) + { + var activityDtos = await myRequest.GetByIdAsync(id); + return activityDtos.ToModel(); + } + + /*! + * \brief Ajoute une activité de manière asynchrone. + * \param activity L'Activity à ajouter. + * \return Une tâche représentant l'opération asynchrone qui retourne l'activité ajouté (Activity). + #1# + public async Task AddActivity(Model.Activity activity) + { + return (await myRequest.PostAsync(activity.ToDto())).ToModel(); + } + + public async Task AddActivity(NewActivityDto activity) + { + throw new NotImplementedException(); + } + + public async Task UpdateActivity(int id, ActivityTinyDto activity) + { + throw new NotImplementedException(); + } + + /*! + * \brief Met à jour une activité de manière asynchrone. + * \param id L'identifiant de l'activité à mettre à jour. + * \param activity Les nouvelles données de l'activité à mettre à jour. + * \return Une tâche représentant l'opération asynchrone qui retourne l'activité mis à jour (Activity). + #1# + public async Task UpdateActivity(int id, Model.Activity activity) + { + var activityDto = activity.ToDto(); + var updatedActivityDto = await myRequest.PutAsync(id, activityDto); + return updatedActivityDto?.ToModel(); + } + + /*! + * \brief Supprime une activité de manière asynchrone. + * \param id L'identifiant de l'activité à supprimer. + * \return Une tâche représentant l'opération asynchrone. + #1# + public async Task DeleteActivity(int id) + { + await myRequest.DeleteAsync(id); + return true; + } + + public Task GetNbItems() + { + return myRequest.GetNbItems(); + } + + public async Task?> GetActivitiesByUser(int userId, int index, int count, ActivityOrderCriteria orderCriteria, bool descending = false) + { + return (await myRequest.GetActivitiesByUser(userId, index, count, orderCriteria, descending)).ToModels(); + } + + public Task GetNbActivitiesByUser(int userId) + { + return myRequest.GetNbActivitiesByUser(userId); + } +}*/ \ No newline at end of file diff --git a/src/Tests/WebAPIConsoleTests/HttpRequest.cs b/src/Tests/WebAPIConsoleTests/HttpRequest.cs new file mode 100644 index 0000000..2b83ab6 --- /dev/null +++ b/src/Tests/WebAPIConsoleTests/HttpRequest.cs @@ -0,0 +1,101 @@ +/*! + * \file HttpRequest.cs + * \brief Fichier contenant la classe HttpRequest. + */ + +using System.Diagnostics; +using System.Net.Http.Json; +using Dto; +using Shared; + +namespace WebAPIConsoleTests; + +/*! + * \brief Classe représentant un client HTTP pour les requêtes vers un service de gestion de elément. + */ +public class HttpRequest where T : class +{ + private HttpClient _httpClient { get; } = new HttpClient(); + public HttpClient HttpClient => _httpClient; + + /*! + * \brief Récupère tous les activitée de manière asynchrone. + * \return Une tâche représentant l'opération asynchrone qui retourne une liste de T. + */ + public async Task> GetAllAsync() + { + return await _httpClient.GetFromJsonAsync>(""); + } + + /*! + * \brief Récupère les élements par index et compte de manière asynchrone. + * \param index L'index de départ pour la pagination. + * \param count Le nombre d'éléments à récupérer. + * \return Une tâche représentant l'opération asynchrone qui retourne une liste de T. + */ + // [TODO] enum + public async Task> GetAsync(Enum criteria, bool descending, int index, int count) + { + return await _httpClient.GetFromJsonAsync>($"?OrderingPropertyName={criteria}&Descending={descending}&Index={index}&Count={count}"); + } + + /*! + * \brief Récupère un elément par son identifiant de manière asynchrone. + * \param id L'identifiant du elément à récupérer. + * \return Une tâche représentant l'opération asynchrone qui retourne une liste de T. + */ + public async Task GetByIdAsync(int id) + { + return await _httpClient.GetFromJsonAsync($"{id}"); + } + + public Task GetNbItems() + { + return _httpClient.GetFromJsonAsync("count"); + } + + public Task?> GetActivitiesByUser(int userId, int index, int count, ActivityOrderCriteria orderCriteria, bool descending = false) + { + return _httpClient.GetFromJsonAsync?>($"?userId={userId}&index={index}&count={count}&orderCriteria={orderCriteria}&descending={descending}"); + } + + public Task GetNbActivitiesByUser(int userId) + { + return _httpClient.GetFromJsonAsync($"count?userId={userId}"); + } + + /*! + * \brief Ajoute une activity de manière asynchrone. + * \param book Le elément à ajouter. + * \return Une tâche représentant l'opération asynchrone qui retourne le activity ajouté (T). + */ + public async Task PostAsync(T activity) + { + var response = await _httpClient.PostAsJsonAsync("", activity); + + return await response.Content.ReadFromJsonAsync(); + } + + /*! + * \brief Met à jour un elément de manière asynchrone. + * \param id L'identifiant du elément à mettre à jour. + * \param book Les nouvelles données du elément à mettre à jour. + * \return Une tâche représentant l'opération asynchrone qui retourne le elément mis à jour (T). + */ + public async Task PutAsync(int id, T activity) + { + var response = await _httpClient.PutAsJsonAsync($"{id}", activity); + + return await response.Content.ReadFromJsonAsync(); + } + + /*! + * \brief Supprime un elément de manière asynchrone. + * \param id L'identifiant du elément à supprimer. + * \return Une tâche représentant l'opération asynchrone. + */ + public async Task DeleteAsync(int id) + { + await _httpClient.DeleteAsync($"{id}"); + } +} \ No newline at end of file diff --git a/src/Tests/WebAPIConsoleTests/Program.cs b/src/Tests/WebAPIConsoleTests/Program.cs new file mode 100644 index 0000000..a7f9075 --- /dev/null +++ b/src/Tests/WebAPIConsoleTests/Program.cs @@ -0,0 +1,66 @@ +using APIMappers; +using Dto; +using Model; +using Model.Repository; +using Shared; +using WebAPIConsoleTests; +Console.WriteLine(""); +/* +IActivityRepository myConsoleTest = new ActivityServiceAPI(); + +// defini un delais d'attente du déploiement de l'API +await Task.Delay(5000); + +// Affiche toutes les activités +Console.WriteLine("Affichage de toutes les Activités : "); +var res = await myConsoleTest.GetActivities(0, 10, ActivityOrderCriteria.ByAthleteId, false); +foreach(var myActivity in res) +{ + Console.WriteLine(myActivity.Id + ", " + myActivity.Type + ", " + myActivity.StartTime + ", " + myActivity.EndTime + ", " + myActivity.DataSource + ", " + myActivity.Athlete); +} + +// Affiche les activités par id +Console.WriteLine("Affichage du livre d'id 1 : "); +res = (IEnumerable)await myConsoleTest.GetActivityByIdAsync(1); +foreach (var myActivity in res) +{ + Console.WriteLine(myActivity.Id + ", " + myActivity.Type + ", " + myActivity.StartTime + ", " + myActivity.EndTime + ", " + myActivity.DataSource + ", " + myActivity.Athlete); +} + +// Ajouter une nouvelle activité +Console.WriteLine("Ajout d'un nouveau livre : "); +var newActivity = new ActivityDto { Type = "New Activity", StartTime = DateTime.Now, EndTime = DateTime.Now, DataSource = new DataSourceDto{}, Athlete = new UserDto{ Username = "Hello", FirstName = "feee", Email = "exemple.com", LastName = "dddd", Sexe = "M" } }; +var addedActivity = await myConsoleTest.AddActivity(newActivity.ToModel()); +Console.WriteLine($"Id: {addedActivity.Id}, Type: {addedActivity.Type}, StartTime: {addedActivity.StartTime}, EndTime: {addedActivity.EndTime}, DataSource: {addedActivity.DataSource}, Athlete: {addedActivity.Athlete}"); + +// Mettre à jour l'activity ajouté +Console.WriteLine("Mise à jour du livre ajouté : "); +var activity = await myConsoleTest.UpdateActivity(1, new ActivityDto { Id = 1, Type = "Updated Activity", StartTime = DateTime.Now, EndTime = DateTime.Now, DataSource = new DataSourceDto{}, Athlete = new UserDto{ Username = "Hello", FirstName = "feee", Email = "exemple.com", LastName = "dddd", Sexe = "M" } }.ToModel()); +Console.WriteLine($"Id: {activity.Id}, Type: {activity.Type}, StartTime: {activity.StartTime}, EndTime: {activity.EndTime}, DataSource: {activity.DataSource}, Athlete: {activity.Athlete}"); + +// Supprimer l'activity ajouté +Console.WriteLine("Suppression du livre ajouté : "); +await myConsoleTest.DeleteActivity(newActivity.Id); +res = await myConsoleTest.GetActivities(0, 10, ActivityOrderCriteria.ByAthleteId, false); +foreach (var activity1 in res) +{ + Console.WriteLine(activity1.Id + ", " + activity1.Type + ", " + activity1.StartTime + ", " + activity1.EndTime + ", " + activity1.DataSource + ", " + activity1.Athlete); +} + +// Affiche le nombre d'activités +Console.WriteLine("Affichage du nombre d'activités : "); +var nb = await myConsoleTest.GetNbItems(); +Console.WriteLine(nb); + +// Affiche les activités par utilisateur +Console.WriteLine("Affichage des activités par utilisateur : "); +res = await myConsoleTest.GetActivitiesByUser(1, 0, 10, ActivityOrderCriteria.ByAthleteId, false); +foreach (var activity3 in res) +{ + Console.WriteLine(activity3.Id + ", " + activity3.Type + ", " + activity3.StartTime + ", " + activity3.EndTime + ", " + activity3.DataSource + ", " + activity3.Athlete); +} + +// Affiche le nombre d'activités par utilisateur +Console.WriteLine("Affichage du nombre d'activités par utilisateur : "); +nb = await myConsoleTest.GetNbActivitiesByUser(1); +Console.WriteLine(nb);*/ \ No newline at end of file diff --git a/src/Tests/WebAPIConsoleTests/WebAPIConsoleTests.csproj b/src/Tests/WebAPIConsoleTests/WebAPIConsoleTests.csproj new file mode 100644 index 0000000..8f402c3 --- /dev/null +++ b/src/Tests/WebAPIConsoleTests/WebAPIConsoleTests.csproj @@ -0,0 +1,17 @@ + + + + + + + + + + + Exe + net8.0 + enable + enable + + +