From 1cc4de979a1d4b0f3b83889d6e7fdfbccc20ea23 Mon Sep 17 00:00:00 2001 From: Matai Date: Tue, 25 Mar 2025 17:40:59 -0400 Subject: [PATCH 01/18] Update packages --- core/api.core.csproj | 7 +++---- emails/api.emails.csproj | 4 ++-- files/api.files.csproj | 8 ++++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/core/api.core.csproj b/core/api.core.csproj index 03ba97c..3fb0058 100644 --- a/core/api.core.csproj +++ b/core/api.core.csproj @@ -17,7 +17,7 @@ - + @@ -25,15 +25,14 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + diff --git a/emails/api.emails.csproj b/emails/api.emails.csproj index a357510..37421d2 100644 --- a/emails/api.emails.csproj +++ b/emails/api.emails.csproj @@ -39,8 +39,8 @@ - - + + diff --git a/files/api.files.csproj b/files/api.files.csproj index 8023c22..1cc7c7e 100644 --- a/files/api.files.csproj +++ b/files/api.files.csproj @@ -7,10 +7,10 @@ - - - - + + + + From beaca8f43f84ffbd730a300488739a29b03dd969 Mon Sep 17 00:00:00 2001 From: Matai Date: Tue, 25 Mar 2025 22:29:58 -0400 Subject: [PATCH 02/18] remove supabase --- .config/dotnet-tools.json | 13 +++ core/Controllers/OrganizersController.cs | 54 ++++++------ core/Controllers/TestController.cs | 18 ++-- .../DependencyInjectionExtension.cs | 11 --- core/Program.cs | 86 ++++++++++--------- core/Properties/launchSettings.json | 4 +- core/Services/Abstractions/IAuthService.cs | 6 -- core/Services/ActivityAreaService.cs | 2 +- core/Services/AuthService.cs | 16 ---- core/Services/EventService.cs | 6 +- core/Services/UserService.cs | 9 +- core/api.core.csproj | 3 + core/appsettings.Development.json | 11 ++- core/appsettings.json | 8 ++ docker-compose.yaml | 40 +++++++++ 15 files changed, 164 insertions(+), 123 deletions(-) create mode 100644 .config/dotnet-tools.json delete mode 100644 core/Services/Abstractions/IAuthService.cs delete mode 100644 core/Services/AuthService.cs create mode 100644 docker-compose.yaml diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..393d187 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "csharpier": { + "version": "0.30.6", + "commands": [ + "dotnet-csharpier" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/core/Controllers/OrganizersController.cs b/core/Controllers/OrganizersController.cs index 12922ec..97e3e47 100644 --- a/core/Controllers/OrganizersController.cs +++ b/core/Controllers/OrganizersController.cs @@ -26,7 +26,6 @@ namespace api.core.controllers; /// for this to work. /// /// Used to fetch and manage the organizers -/// Used to create a new user in the Supabase database /// Used to send an email to the newly created organizer /// Used to fetch the FRONTEND_BASE_URL from the environments variables [Authorize(Policy = AuthPolicies.IsModerator)] @@ -34,7 +33,6 @@ namespace api.core.controllers; [Route("api/organizers")] public class ModeratorUserController( IUserService userService, - IAuthService authService, IEmailService emailService, IConfiguration configuration) : ControllerBase { @@ -68,32 +66,32 @@ public IActionResult GetOrganizer(Guid organizerId) /// /// /// - [HttpPost] - public async Task CreateOrganizer([FromBody] UserCreateDTO organizer) - { - var strongPassword = GenerateRandomPassword(12); - var supabaseUser = authService.SignUp(organizer.Email, strongPassword); - _ = Guid.TryParse(supabaseUser, out Guid userId); - var created = userService.AddOrganizer(userId, organizer); - var frontBaseUrl = configuration.GetValue("FRONTEND_BASE_URL") ?? throw new ArgumentNullException("FRONTEND_BASE_URL is not set"); - await emailService.SendEmailAsync( - organizer.Email, - "Votre compte Hello!", - new UserCreationModel - { - Title = "Création de votre compte Hello!", - Salutation = $"Bonjour {organizer.Organization},", - AccountCreatedText = "Votre compte Hello a été créé!", - TemporaryPasswordHeader = "Votre mot de passe temporaire est: ", - TemporaryPassword = strongPassword, - LoginButtonText = "Se connecter", - ButtonLink = new Uri($"{frontBaseUrl}/fr/login") - }, - emails.EmailsUtils.UserCreationTemplate - ); - - return Ok(new Response { Data = created }); - } + //[HttpPost] + //public async Task CreateOrganizer([FromBody] UserCreateDTO organizer) + //{ + // var strongPassword = GenerateRandomPassword(12); + // var supabaseUser = authService.SignUp(organizer.Email, strongPassword); + // _ = Guid.TryParse(supabaseUser, out Guid userId); + // var created = userService.AddOrganizer(userId, organizer); + // var frontBaseUrl = configuration.GetValue("FRONTEND_BASE_URL") ?? throw new ArgumentNullException("FRONTEND_BASE_URL is not set"); + // await emailService.SendEmailAsync( + // organizer.Email, + // "Votre compte Hello!", + // new UserCreationModel + // { + // Title = "Création de votre compte Hello!", + // Salutation = $"Bonjour {organizer.Organization},", + // AccountCreatedText = "Votre compte Hello a été créé!", + // TemporaryPasswordHeader = "Votre mot de passe temporaire est: ", + // TemporaryPassword = strongPassword, + // LoginButtonText = "Se connecter", + // ButtonLink = new Uri($"{frontBaseUrl}/fr/login") + // }, + // emails.EmailsUtils.UserCreationTemplate + // ); + + // return Ok(new Response { Data = created }); + //} /// /// Get all users with pagination and search term diff --git a/core/Controllers/TestController.cs b/core/Controllers/TestController.cs index 4c6f4a8..57184f5 100644 --- a/core/Controllers/TestController.cs +++ b/core/Controllers/TestController.cs @@ -14,14 +14,14 @@ namespace api.core.controllers; public class TestController(IConfiguration configuration) : ControllerBase { - [HttpPost("login")] - public async Task Login([FromBody] LoginRequestDTO req, CancellationToken ct) - { - var projectId = configuration.GetValue("SUPABASE_PROJECT_ID"); - var anonKey = configuration.GetValue("SUPABASE_ANON_KEY"); + //[HttpPost("login")] + //public async Task Login([FromBody] LoginRequestDTO req, CancellationToken ct) + //{ + //var projectId = configuration.GetValue("SUPABASE_PROJECT_ID"); + //var anonKey = configuration.GetValue("SUPABASE_ANON_KEY"); - var client = new Supabase.Client($"https://{projectId}.supabase.co", anonKey); - var response = await client.Auth.SignInWithPassword(req.Email, req.Password); - return Ok(response); - } + //var client = new Supabase.Client($"https://{projectId}.supabase.co", anonKey); + //var response = await client.Auth.SignInWithPassword(req.Email, req.Password); + //return Ok(response); + //} } \ No newline at end of file diff --git a/core/Extensions/DependencyInjectionExtension.cs b/core/Extensions/DependencyInjectionExtension.cs index 8e77108..92f5d82 100644 --- a/core/Extensions/DependencyInjectionExtension.cs +++ b/core/Extensions/DependencyInjectionExtension.cs @@ -9,16 +9,12 @@ using api.files.Services; using api.files.Services.Abstractions; -using Supabase; - namespace api.core.Extensions; public static class DependencyInjectionExtension { public static IServiceCollection AddDependencyInjection(this IServiceCollection services) { - AddSupabase(services); - // Middlewares services.AddTransient(); @@ -38,7 +34,6 @@ public static IServiceCollection AddDependencyInjection(this IServiceCollection services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -50,10 +45,4 @@ public static IServiceCollection AddDependencyInjection(this IServiceCollection return services; } - private static void AddSupabase(IServiceCollection services) - { - var url = $"https://{Environment.GetEnvironmentVariable("SUPABASE_PROJECT_ID")}.supabase.co"; - var key = Environment.GetEnvironmentVariable("SUPABASE_ANON_KEY"); - services.AddSingleton(provider => new Client(url, key)); - } } \ No newline at end of file diff --git a/core/Program.cs b/core/Program.cs index 28d5cd4..2260233 100644 --- a/core/Program.cs +++ b/core/Program.cs @@ -1,48 +1,54 @@ -using api.core.Extensions; - +using System.Reflection; using System.Text; using api.core.data; +using api.core.Extensions; +using api.core.Misc; +using api.emails; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; +using Microsoft.Identity.Web; +using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; -using api.core.Misc; -using api.emails; -using System.Reflection; + + +IdentityModelEventSource.ShowPII = true; + var builder = WebApplication.CreateBuilder(args); // Environments setup -string supabaseSecretKey = null!; -string supabaseProjectId = null!; string connectionString = null!; string? redisConnString = null!; -connectionString = Environment.GetEnvironmentVariable("CONNECTION_STRING") ?? throw new Exception("CONNECTION_STRING is not set"); +connectionString = + Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? throw new Exception("CONNECTION_STRING is not set"); redisConnString = Environment.GetEnvironmentVariable("REDIS_CONNECTION_STRING"); -if (!EF.IsDesignTime) -{ - supabaseSecretKey = Environment.GetEnvironmentVariable("SUPABASE_SECRET_KEY") ?? throw new Exception("SUPABASE_SECRET_KEY is not set"); - supabaseProjectId = Environment.GetEnvironmentVariable("SUPABASE_PROJECT_ID") ?? throw new Exception("SUPABASE_PROJECT_ID is not set"); -} - builder.Configuration.AddEnvironmentVariables(); builder.Services.AddDbContext(opt => opt.UseNpgsql(connectionString)); -builder.Services.AddAuthentication().AddJwtBearer(o => -{ - o.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(supabaseSecretKey)), - ValidAudiences = ["authenticated"], - ValidIssuer = $"https://{supabaseProjectId}.supabase.co/auth/v1" - }; -}); +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.RequireHttpsMetadata = false; + options.SaveToken = true; + options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = "https://login.microsoftonline.com/188c27a3-86bf-4988-9c94-025a75fcf0d1/v2.0", + ValidAudience = "bf42ef76-b599-4ab1-a015-6e4b8afa347b", + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("")) + }; + }); builder.Services.SetupScheduler(); @@ -56,8 +62,7 @@ builder.Services.AddOutputCache(options => { - options.AddBasePolicy(builder => - builder.Cache()); + options.AddBasePolicy(builder => builder.Cache()); }); // Errors handling @@ -67,8 +72,7 @@ // Endpoints builder.Services.AddControllers(); -builder.Services.AddHealthChecks() - .AddNpgSql(connectionString); +builder.Services.AddHealthChecks().AddNpgSql(connectionString); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => @@ -81,22 +85,22 @@ BearerFormat = "JWT", In = ParameterLocation.Header, Description = "JWT Authorization header using the Bearer scheme. " + - "\r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.", + "\r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.", }); options.AddSecurityRequirement(new OpenApiSecurityRequirement { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Bearer" - } - }, - Array.Empty() - } + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } }); options.UseInlineDefinitionsForEnums(); @@ -121,11 +125,11 @@ app.UseSwagger(); app.UseSwaggerUI(); - app.UseExceptionMiddleware(); // app.UseHttpsRedirection(); +app.UseAuthentication(); app.UseAuthorization(); app.MapHealthChecks("/health"); diff --git a/core/Properties/launchSettings.json b/core/Properties/launchSettings.json index f427a98..d2a1975 100644 --- a/core/Properties/launchSettings.json +++ b/core/Properties/launchSettings.json @@ -15,7 +15,9 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "REDIS_CONNECTION_STRING": "localhost:63790,password=eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81,abortConnect=False,resolvedns=1", + "CONNECTION_STRING": "User ID=postgres;Password=test123;Host=localhost;Port=5432;Database=ps;" }, "dotnetRunMessages": true, "applicationUrl": "https://localhost:7272;http://localhost:5010" diff --git a/core/Services/Abstractions/IAuthService.cs b/core/Services/Abstractions/IAuthService.cs deleted file mode 100644 index 8de4bda..0000000 --- a/core/Services/Abstractions/IAuthService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace api.core.services.abstractions; - -public interface IAuthService -{ - string SignUp(string email, string password); -} diff --git a/core/Services/ActivityAreaService.cs b/core/Services/ActivityAreaService.cs index 276969e..fe1b315 100644 --- a/core/Services/ActivityAreaService.cs +++ b/core/Services/ActivityAreaService.cs @@ -14,7 +14,7 @@ public IEnumerable GetAllActivityAreas(string? search) { return activityAreaRepository.GetAll() .Where(aa => - (search.IsNullOrEmpty() || + (search == null || search.Equals("") || aa.NameFr.ToLower().Contains(search!.ToLower() ?? "") || aa.NameEn.ToLower().Contains(search!.ToLower() ?? "")) && aa.DeletedAt == null) diff --git a/core/Services/AuthService.cs b/core/Services/AuthService.cs deleted file mode 100644 index c93358a..0000000 --- a/core/Services/AuthService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using api.core.services.abstractions; - -using Supabase; - -namespace api.core.Services; - -public class AuthService(Client client) : IAuthService -{ - public string SignUp(string email, string password) - { - var user = client.Auth.SignUp(email, password).Result; - - return user?.User?.Id - ?? throw new Exception("An error occured while signing up the user"); - } -} diff --git a/core/Services/EventService.cs b/core/Services/EventService.cs index ea44fcb..4153dff 100644 --- a/core/Services/EventService.cs +++ b/core/Services/EventService.cs @@ -52,8 +52,8 @@ public IEnumerable GetEvents( (state.HasFlag(e.Publication.State)) && (organizerId == null || e.Publication.OrganizerId == organizerId) && (title == null || (e.Publication.Title != null && e.Publication.Title.ToLower().Contains(title.ToLower()))) && - (tags.IsNullOrEmpty() || e.Publication.Tags.Any(t => tags!.Any(tt => t.Id == tt))) && - (activityAreas.IsNullOrEmpty() || activityAreas!.Any(aa => aa == e.Publication.Organizer.ActivityAreaId))) + (tags.Count() < 1 || e.Publication.Tags.Any(t => tags!.Any(tt => t.Id == tt))) && + (activityAreas.Count() < 1 || activityAreas!.Any(aa => aa == e.Publication.Organizer.ActivityAreaId))) .AsQueryable() .OrderBy(orderBy, desc) .Select(EventResponseDTO.Map); @@ -167,7 +167,7 @@ public bool DeleteEvent(Guid userId, Guid eventId) public bool UpdateEvent(Guid userId, Guid eventId, EventUpdateRequestDTO request) { - _ = orgRepo.Get(userId) + _ = orgRepo.Get(userId) ?? throw new UnauthorizedException(); if (request.Tags.Count > 5) diff --git a/core/Services/UserService.cs b/core/Services/UserService.cs index 9d4f3c0..0068832 100644 --- a/core/Services/UserService.cs +++ b/core/Services/UserService.cs @@ -3,15 +3,12 @@ using api.core.Data.Enums; using api.core.Data.requests; using api.core.Data.Responses; -using api.core.repositories; using api.core.repositories.abstractions; using api.core.services.abstractions; using api.files.Services.Abstractions; -using Microsoft.IdentityModel.Tokens; using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Processing; namespace api.core.Services; @@ -49,13 +46,13 @@ public UserResponseDTO AddOrganizer(Guid id, UserCreateDTO organizerDto) var avatarUri = fileShareService.FileGetDownloadUri($"{id}/{AVATAR_FILE_NAME}"); var user = UserResponseDTO.Map(inserted); user.AvatarUrl = avatarUri.ToString(); - + return user; } public UserResponseDTO GetUser(Guid id) { - UserResponseDTO? userRes = null; + UserResponseDTO? userRes = null; var organizer = organizerRepository.Get(id); if (organizer != null) userRes = UserResponseDTO.Map(organizer!); @@ -84,7 +81,7 @@ public string GetUserAvatarUrl(Guid id) public IEnumerable GetUsers(string? search, OrganizerAccountActiveFilter activeFilter, out int count) { var organizers = organizerRepository.GetAll() - .Where(x => (search.IsNullOrEmpty() || + .Where(x => (search == null || search.Equals("") || x.Organization.ToLower().Contains(search!.ToLower() ?? "") || x.Email.ToLower().Contains(search!.ToLower() ?? "")) && ((activeFilter.HasFlag(OrganizerAccountActiveFilter.Active) && x.IsActive) || diff --git a/core/api.core.csproj b/core/api.core.csproj index 3fb0058..204e78c 100644 --- a/core/api.core.csproj +++ b/core/api.core.csproj @@ -17,6 +17,8 @@ + + @@ -24,6 +26,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/core/appsettings.Development.json b/core/appsettings.Development.json index 0c208ae..d3831e8 100644 --- a/core/appsettings.Development.json +++ b/core/appsettings.Development.json @@ -1,8 +1,17 @@ { + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "188c27a3-86bf-4988-9c94-025a75fcf0d1", + "ClientId": "bf42ef76-b599-4ab1-a015-6e4b8afa347b", + "Issuer": "https://login.microsoftonline.com/188c27a3-86bf-4988-9c94-025a75fcf0d1/v2.0", + "Scopes": "User.Read", + "Audience": "bf42ef76-b599-4ab1-a015-6e4b8afa347b" + }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } - } + }, + "AllowedHosts": "*" } diff --git a/core/appsettings.json b/core/appsettings.json index 10f68b8..d3831e8 100644 --- a/core/appsettings.json +++ b/core/appsettings.json @@ -1,4 +1,12 @@ { + "AzureAd": { + "Instance": "https://login.microsoftonline.com/", + "TenantId": "188c27a3-86bf-4988-9c94-025a75fcf0d1", + "ClientId": "bf42ef76-b599-4ab1-a015-6e4b8afa347b", + "Issuer": "https://login.microsoftonline.com/188c27a3-86bf-4988-9c94-025a75fcf0d1/v2.0", + "Scopes": "User.Read", + "Audience": "bf42ef76-b599-4ab1-a015-6e4b8afa347b" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..3fee42a --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,40 @@ +version: '3.9' +services: + backend: + image: ghcr.io/applets/backend-hello:main + restart: always + ports: + - 8080:8080 + env_file: + - core/.env + volumes: + - ./backend-data:/app/volume:rw + adminer: + image: adminer + restart: always + ports: + - 8090:8080 + db: + image: postgres + restart: always + ports: + - 5432:5432 + env_file: + - core/.env + redis: + image: redis:7.2.4 + restart: always + ports: + - '63790:6379' + command: redis-server --save 20 1 --loglevel warning --requirepass eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81 + volumes: + - cache:/data + cdn: + image: nginx:1.25.3 + restart: always + ports: + - 6464:80 +volumes: + cache: + driver: local + \ No newline at end of file From 15015159712e0fd800fdd04e7ee0817dba7ac3d1 Mon Sep 17 00:00:00 2001 From: superjekk Date: Wed, 24 Sep 2025 10:02:58 -0400 Subject: [PATCH 03/18] =?UTF-8?q?NETTOYAGE=20:=20Art=C3=A9facts=20de=20Sup?= =?UTF-8?q?abase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les vestiges de Supabase ont été retirés du serveur --- core/.env.template | 3 --- core/Data/Requests/LoginRequestDTO.cs | 7 ------- 2 files changed, 10 deletions(-) delete mode 100644 core/Data/Requests/LoginRequestDTO.cs diff --git a/core/.env.template b/core/.env.template index 076ff64..fb982d9 100644 --- a/core/.env.template +++ b/core/.env.template @@ -5,9 +5,6 @@ ASPNETCORE_HTTP_PORT=8080 ASPNETCORE_ENVIRONMENT=Development CONNECTION_STRING=User ID=postgres;Password=test123;Host=host.docker.internal;Port=5432;Database=ps; -SUPABASE_PROJECT_ID= -SUPABASE_SECRET_KEY= -SUPABASE_ANON_KEY= EMAIL_SERVER=smtp.gmail.com EMAIL_PORT=587 diff --git a/core/Data/Requests/LoginRequestDTO.cs b/core/Data/Requests/LoginRequestDTO.cs deleted file mode 100644 index 5c3ca48..0000000 --- a/core/Data/Requests/LoginRequestDTO.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace api.core.Data.Requests; - -public class LoginRequestDTO -{ - public required string Email { get; set; } - public required string Password { get; set; } -} From 89f4e11527cd81eb493c0be5da890e34cf65d539 Mon Sep 17 00:00:00 2001 From: superjekk Date: Thu, 6 Nov 2025 13:15:46 -0500 Subject: [PATCH 04/18] PROGRES : Connexion entre Authentik et Babillard --- core/.env.template | 5 +++++ core/Controllers/TestController.cs | 26 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/core/.env.template b/core/.env.template index fb982d9..8724981 100644 --- a/core/.env.template +++ b/core/.env.template @@ -18,6 +18,11 @@ REDIS_CONNECTION_STRING=host.docker.internal:6379,password=eYVX7EwVmmxKPCDmwMtyK CONTAINER_DIR=/app/volume CDN_URL=http://localhost:6464 +OPENID_ISSUER=https://example.com +OPENID_BASE_URL=[Insert your URL here] +OPENID_CLIENT_ID=[Insert your client id here] +OPENID_CLIENT_SECRET=[Insert your client secret here] + FRONTEND_BASE_URL=http://localhost:3000 RATE_LIMIT_TIME_WINDOW_SECONDS=10 diff --git a/core/Controllers/TestController.cs b/core/Controllers/TestController.cs index 57184f5..63208c5 100644 --- a/core/Controllers/TestController.cs +++ b/core/Controllers/TestController.cs @@ -1,4 +1,4 @@ -using api.core.Data.Requests; +using api.core.Data.Requests; using Microsoft.AspNetCore.Mvc; @@ -14,14 +14,20 @@ namespace api.core.controllers; public class TestController(IConfiguration configuration) : ControllerBase { - //[HttpPost("login")] - //public async Task Login([FromBody] LoginRequestDTO req, CancellationToken ct) - //{ - //var projectId = configuration.GetValue("SUPABASE_PROJECT_ID"); - //var anonKey = configuration.GetValue("SUPABASE_ANON_KEY"); + [HttpGet] + public IActionResult Login() + { + var redirectionURL = Environment.GetEnvironmentVariable("OPENID_BASE_URL") + "authorize/?"; + + Dictionary queryParameters = new(); + + queryParameters["client_id"] = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"); + queryParameters["response_type"] = "code"; + queryParameters["redirect_uri"] = "http://localhost:8080"; + queryParameters["scope"] = "email"; + //queryParameters["response_type"] = "code"; + + return Redirect(redirectionURL + string.Join('&', queryParameters.Select(qp => qp.Key + '=' + qp.Value))); + } - //var client = new Supabase.Client($"https://{projectId}.supabase.co", anonKey); - //var response = await client.Auth.SignInWithPassword(req.Email, req.Password); - //return Ok(response); - //} } \ No newline at end of file From c26346c12bea15203f43c81e202b33dabf926146 Mon Sep 17 00:00:00 2001 From: superjekk Date: Mon, 10 Nov 2025 13:00:39 -0500 Subject: [PATCH 05/18] =?UTF-8?q?PROGRES=20:=20R=C3=A9ception=20de=20code?= =?UTF-8?q?=20et=20authentification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/Controllers/TestController.cs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/core/Controllers/TestController.cs b/core/Controllers/TestController.cs index 63208c5..c423101 100644 --- a/core/Controllers/TestController.cs +++ b/core/Controllers/TestController.cs @@ -1,6 +1,7 @@ -using api.core.Data.Requests; +using api.core.Data.Requests; using Microsoft.AspNetCore.Mvc; +using Microsoft.Identity.Client.Platforms.Features.DesktopOs.Kerberos; namespace api.core.controllers; @@ -25,9 +26,34 @@ public IActionResult Login() queryParameters["response_type"] = "code"; queryParameters["redirect_uri"] = "http://localhost:8080"; queryParameters["scope"] = "email"; - //queryParameters["response_type"] = "code"; + queryParameters["state"] = "1234"; return Redirect(redirectionURL + string.Join('&', queryParameters.Select(qp => qp.Key + '=' + qp.Value))); } + [HttpGet] + [Route("/")] + public async Task Reception([FromQuery] string code, [FromQuery] string? state) + { + using HttpClient client = new(new HttpClientHandler + { + PreAuthenticate = true, + }) +; + string claimUrl = Environment.GetEnvironmentVariable("OPENID_BASE_URL") + "token/"; + + Dictionary body = new() + { + ["grant_type"] = "authorization_code", + ["redirect_uri"] = Request.Scheme + "://" + Request.Host.Value, + ["code"] = code + }; + + HttpResponseMessage response = await client.PostAsync(claimUrl, new FormUrlEncodedContent(body)); + + + + + return Ok(); + } } \ No newline at end of file From c1c33b323baa229a51ce6556f5950b6911b6efe7 Mon Sep 17 00:00:00 2001 From: superjekk Date: Tue, 11 Nov 2025 14:25:43 -0500 Subject: [PATCH 06/18] =?UTF-8?q?FCT=20:=20R=C3=A9ception=20de=20code=20d'?= =?UTF-8?q?identification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le serveur est maintenant capable de récupérer un jeton d'identification depuis Authentik et de le traiter pour le rendre utilisable --- core/Controllers/TestController.cs | 61 ++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/core/Controllers/TestController.cs b/core/Controllers/TestController.cs index c423101..bb270c3 100644 --- a/core/Controllers/TestController.cs +++ b/core/Controllers/TestController.cs @@ -1,7 +1,9 @@ -using api.core.Data.Requests; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Identity.Client.Platforms.Features.DesktopOs.Kerberos; namespace api.core.controllers; @@ -20,13 +22,14 @@ public IActionResult Login() { var redirectionURL = Environment.GetEnvironmentVariable("OPENID_BASE_URL") + "authorize/?"; - Dictionary queryParameters = new(); - - queryParameters["client_id"] = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"); - queryParameters["response_type"] = "code"; - queryParameters["redirect_uri"] = "http://localhost:8080"; - queryParameters["scope"] = "email"; - queryParameters["state"] = "1234"; + Dictionary queryParameters = new() + { + ["client_id"] = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"), + ["response_type"] = "code", + ["redirect_uri"] = "http://localhost:8080", + ["scope"] = "email", + //["state"] = "1234" + }; return Redirect(redirectionURL + string.Join('&', queryParameters.Select(qp => qp.Key + '=' + qp.Value))); } @@ -35,11 +38,7 @@ public IActionResult Login() [Route("/")] public async Task Reception([FromQuery] string code, [FromQuery] string? state) { - using HttpClient client = new(new HttpClientHandler - { - PreAuthenticate = true, - }) -; + using HttpClient client = new(); string claimUrl = Environment.GetEnvironmentVariable("OPENID_BASE_URL") + "token/"; Dictionary body = new() @@ -49,11 +48,41 @@ public async Task Reception([FromQuery] string code, [FromQuery] ["code"] = code }; - HttpResponseMessage response = await client.PostAsync(claimUrl, new FormUrlEncodedContent(body)); + string clientId = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"); + string clientSecret = Environment.GetEnvironmentVariable("OPENID_CLIENT_SECRET"); + + using HttpRequestMessage request = new(HttpMethod.Post, claimUrl); + + request.Content = new FormUrlEncodedContent(body); + request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"))); + + HttpResponseMessage response = await client.SendAsync(request); + if (! response.IsSuccessStatusCode) + { + return BadRequest(); + } + + string contenu = await response.Content.ReadAsStringAsync(); + JsonSerializerOptions settings = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower + }; + TokenResponse token = JsonSerializer.Deserialize(contenu, settings)!; - return Ok(); + return Ok(token); } +} + +[JsonSerializable(typeof(TokenResponse))] +[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)] +public record class TokenResponse +{ + public string AccessToken { get; set; } + public string TokenType { get; set; } + public string Scope { get; set; } + public string IdToken { get; set; } + public int ExpiresIn { get; set; } } \ No newline at end of file From 2d12fdceaae2976d830b6d89bdcb9860dde6321c Mon Sep 17 00:00:00 2001 From: superjekk Date: Tue, 25 Nov 2025 14:46:16 -0500 Subject: [PATCH 07/18] PROGRES : Validation OpenID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La validation d'identité est partiellement complétée sur le serveur; un point d'entrée protégé redirige vers le fournisseur d'identité, mais le fournisseur n'arrive pas à avoir un état valide --- core/Program.cs | 72 ++++++++++++++++++++++++++++++++++---------- core/api.core.csproj | 1 + 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/core/Program.cs b/core/Program.cs index 2260233..d1a2820 100644 --- a/core/Program.cs +++ b/core/Program.cs @@ -7,16 +7,17 @@ using api.emails; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Web; using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; IdentityModelEventSource.ShowPII = true; - var builder = WebApplication.CreateBuilder(args); // Environments setup @@ -26,27 +27,65 @@ connectionString = Environment.GetEnvironmentVariable("CONNECTION_STRING") ?? throw new Exception("CONNECTION_STRING is not set"); - redisConnString = Environment.GetEnvironmentVariable("REDIS_CONNECTION_STRING"); builder.Configuration.AddEnvironmentVariables(); builder.Services.AddDbContext(opt => opt.UseNpgsql(connectionString)); -builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => +builder.Services + .AddAuthentication(options => + { + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = "oicd"; + }) + .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => + { + options.RequireHttpsMetadata = false; + options.SaveToken = true; + options.Authority = Environment.GetEnvironmentVariable("OPENID_ISSUER"); + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidIssuer = Environment.GetEnvironmentVariable("OPENID_ISSUER"), + ValidAudience = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"), + NameClaimType = "email" + }; + }) + .AddOpenIdConnect("oicd", options => { - options.RequireHttpsMetadata = false; - options.SaveToken = true; - options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters + options.Authority = Environment.GetEnvironmentVariable("OPENID_ISSUER"); + options.ClientId = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"); + options.ClientSecret = Environment.GetEnvironmentVariable("OPENID_CLIENT_SECRET"); + options.ResponseType = OpenIdConnectResponseType.Code; + + options.SaveTokens = false; + + // TODO : Mettre les scopes requis + + options.Scope.Add("openid"); + options.Scope.Add("email"); + options.Scope.Add("profile"); + + options.GetClaimsFromUserInfoEndpoint = true; + + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = "email" + }; + + options.Events = new OpenIdConnectEvents { - ValidateIssuer = true, - ValidateAudience = true, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - ValidIssuer = "https://login.microsoftonline.com/188c27a3-86bf-4988-9c94-025a75fcf0d1/v2.0", - ValidAudience = "bf42ef76-b599-4ab1-a015-6e4b8afa347b", - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("")) + OnTokenValidated = context => + { + return Task.CompletedTask; + }, + OnAuthenticationFailed = context => + { + return Task.CompletedTask; + } }; }); @@ -83,9 +122,10 @@ Type = SecuritySchemeType.ApiKey, Scheme = "Bearer", BearerFormat = "JWT", - In = ParameterLocation.Header, + In = ParameterLocation.Cookie, Description = "JWT Authorization header using the Bearer scheme. " + - "\r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.", + "\r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.", + OpenIdConnectUrl = new Uri(Environment.GetEnvironmentVariable("OPENID_ISSUER")!) }); options.AddSecurityRequirement(new OpenApiSecurityRequirement diff --git a/core/api.core.csproj b/core/api.core.csproj index 204e78c..d02811e 100644 --- a/core/api.core.csproj +++ b/core/api.core.csproj @@ -17,6 +17,7 @@ + From 43d8fb6bda012eecbfa2b0641275de6684ecfd14 Mon Sep 17 00:00:00 2001 From: Matai Date: Tue, 2 Dec 2025 15:50:09 -0500 Subject: [PATCH 08/18] oidc configs fix --- core/Controllers/TestController.cs | 9 +- core/Program.cs | 139 ++++++++++++++++------------ core/Properties/launchSettings.json | 105 ++++++++++----------- docker-compose.yaml | 2 + 4 files changed, 135 insertions(+), 120 deletions(-) diff --git a/core/Controllers/TestController.cs b/core/Controllers/TestController.cs index bb270c3..b4191b7 100644 --- a/core/Controllers/TestController.cs +++ b/core/Controllers/TestController.cs @@ -21,21 +21,20 @@ public class TestController(IConfiguration configuration) : ControllerBase public IActionResult Login() { var redirectionURL = Environment.GetEnvironmentVariable("OPENID_BASE_URL") + "authorize/?"; - Dictionary queryParameters = new() { ["client_id"] = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"), ["response_type"] = "code", - ["redirect_uri"] = "http://localhost:8080", - ["scope"] = "email", - //["state"] = "1234" + ["redirect_uri"] = "https://localhost:8081/callback/code", + ["scope"] = "email,profile,openid", + ["state"] = "1234" }; return Redirect(redirectionURL + string.Join('&', queryParameters.Select(qp => qp.Key + '=' + qp.Value))); } [HttpGet] - [Route("/")] + [Route("/callback/code")] public async Task Reception([FromQuery] string code, [FromQuery] string? state) { using HttpClient client = new(); diff --git a/core/Program.cs b/core/Program.cs index d1a2820..298c46a 100644 --- a/core/Program.cs +++ b/core/Program.cs @@ -4,14 +4,11 @@ using api.core.data; using api.core.Extensions; using api.core.Misc; -using api.emails; - +using api.emails; + using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.EntityFrameworkCore; -using Microsoft.Identity.Web; using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; @@ -31,64 +28,84 @@ builder.Configuration.AddEnvironmentVariables(); -builder.Services.AddDbContext(opt => opt.UseNpgsql(connectionString)); +builder.Services.AddDbContext(opt => opt.UseNpgsql(connectionString)); + +var key = Encoding.ASCII.GetBytes(Environment.GetEnvironmentVariable("OPENID_CLIENT_SECRET") ?? ""); builder.Services .AddAuthentication(options => { - options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = "oicd"; - }) - .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => - { - options.RequireHttpsMetadata = false; - options.SaveToken = true; - options.Authority = Environment.GetEnvironmentVariable("OPENID_ISSUER"); - - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidIssuer = Environment.GetEnvironmentVariable("OPENID_ISSUER"), - ValidAudience = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"), - NameClaimType = "email" - }; - }) - .AddOpenIdConnect("oicd", options => - { - options.Authority = Environment.GetEnvironmentVariable("OPENID_ISSUER"); - options.ClientId = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"); - options.ClientSecret = Environment.GetEnvironmentVariable("OPENID_CLIENT_SECRET"); - options.ResponseType = OpenIdConnectResponseType.Code; - - options.SaveTokens = false; - - // TODO : Mettre les scopes requis - - options.Scope.Add("openid"); - options.Scope.Add("email"); - options.Scope.Add("profile"); - - options.GetClaimsFromUserInfoEndpoint = true; - - options.TokenValidationParameters = new TokenValidationParameters - { - NameClaimType = "email" - }; - - options.Events = new OpenIdConnectEvents - { - OnTokenValidated = context => - { - return Task.CompletedTask; - }, - OnAuthenticationFailed = context => - { - return Task.CompletedTask; - } - }; - }); - + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + //.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => + //{ + // options.RequireHttpsMetadata = false; + // options.SaveToken = true; + // options.Authority = Environment.GetEnvironmentVariable("OPENID_ISSUER"); + + // options.TokenValidationParameters = new TokenValidationParameters + // { + // ValidateIssuer = true, + // ValidateAudience = true, + // ValidIssuer = Environment.GetEnvironmentVariable("OPENID_ISSUER"), + // ValidAudience = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"), + // NameClaimType = "email" + // }; + //}) + .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => + { + // The URL of your Identity Provider (e.g., "https://dev-xyz.us.auth0.com/") + // The API will download the public keys from here automatically. + options.Authority = Environment.GetEnvironmentVariable("OPENID_ISSUER"); + + // Who is this token for? (This usually matches the "API Identifier" in your IdP) + options.Audience = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"); + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = true, + ValidateAudience = true, + ValidAudience = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID") + }; + // Ensure HTTPS is used (should be true in production) + options.RequireHttpsMetadata = true; + }); +//.AddOpenIdConnect(options => +// { +// options.Authority = Environment.GetEnvironmentVariable("OPENID_ISSUER"); +// options.ClientId = Environment.GetEnvironmentVariable("OPENID_CLIENT_ID"); +// //options.ClientSecret = Environment.GetEnvironmentVariable("OPENID_CLIENT_SECRET"); + +// //options.SaveTokens = false; + +// //// TODO : Mettre les scopes requis + +// options.Scope.Add("openid"); +// options.Scope.Add("email"); +// options.Scope.Add("profile"); + +// //options.GetClaimsFromUserInfoEndpoint = true; + +// //options.TokenValidationParameters = new TokenValidationParameters +// //{ +// // NameClaimType = "email" +// //}; + +// //options.Events = new OpenIdConnectEvents +// //{ +// // OnTokenValidated = context => +// // { +// // return Task.CompletedTask; +// // }, +// // OnAuthenticationFailed = context => +// // { +// // return Task.CompletedTask; +// // } +// //}; +// }); + builder.Services.SetupScheduler(); if (string.IsNullOrEmpty(redisConnString)) @@ -119,10 +136,10 @@ options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() { Name = "Authorization", - Type = SecuritySchemeType.ApiKey, + Type = SecuritySchemeType.Http, Scheme = "Bearer", BearerFormat = "JWT", - In = ParameterLocation.Cookie, + In = ParameterLocation.Header, Description = "JWT Authorization header using the Bearer scheme. " + "\r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.", OpenIdConnectUrl = new Uri(Environment.GetEnvironmentVariable("OPENID_ISSUER")!) diff --git a/core/Properties/launchSettings.json b/core/Properties/launchSettings.json index d2a1975..bbea7fc 100644 --- a/core/Properties/launchSettings.json +++ b/core/Properties/launchSettings.json @@ -1,55 +1,52 @@ -{ - "profiles": { - "http": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "dotnetRunMessages": true, - "applicationUrl": "http://localhost:5010" - }, - "https": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "REDIS_CONNECTION_STRING": "localhost:63790,password=eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81,abortConnect=False,resolvedns=1", - "CONNECTION_STRING": "User ID=postgres;Password=test123;Host=localhost;Port=5432;Database=ps;" - }, - "dotnetRunMessages": true, - "applicationUrl": "https://localhost:7272;http://localhost:5010" - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Docker": { - "commandName": "Docker", - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", - "environmentVariables": { - "ASPNETCORE_HTTP_PORTS": "8080" - }, - "publishAllPorts": true, - "useSSL": true, - "httpPort": 8080, - "sslPort": 8081, - "DockerfileRunArguments": "--rm --env-file .env" - } - }, - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:58639", - "sslPort": 44369 - } - } +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5010" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "REDIS_CONNECTION_STRING": "localhost:63790,password=eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81,abortConnect=False,resolvedns=1", + "CONNECTION_STRING": "User ID=postgres;Password=test123;Host=localhost;Port=5432;Database=ps;" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7272;http://localhost:5010" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "publishAllPorts": true, + "useSSL": true, + "httpPort": 8080, + "sslPort": 8081, + "DockerfileRunArguments": "--rm --env-file .env" + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:58639", + "sslPort": 44369 + } + } } \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 3fee42a..9c6fa6a 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -19,6 +19,8 @@ services: restart: always ports: - 5432:5432 + environment: + - POSTGRES_PASSWORD=test123 env_file: - core/.env redis: From 914faa7e6ac5a0a293472cee931906a6b89822d5 Mon Sep 17 00:00:00 2001 From: superjekk Date: Tue, 9 Dec 2025 11:24:14 -0500 Subject: [PATCH 09/18] =?UTF-8?q?PROGRES=20:=20M=C3=A9tamorphose=20de=20la?= =?UTF-8?q?=20gestion=20d'identit=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La méthode utilitaire permettant de récupérer l'identifiant de l'utilisateur a été modifiée afin de refléter la nouvelle technologie d'identité --- core/Misc/JwtUtils.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/core/Misc/JwtUtils.cs b/core/Misc/JwtUtils.cs index f042695..5119808 100644 --- a/core/Misc/JwtUtils.cs +++ b/core/Misc/JwtUtils.cs @@ -4,7 +4,7 @@ namespace api.core.Misc; public static class JwtUtils { - public static Guid GetUserIdFromAuthHeader(string authHeader) + public static string GetUserIdFromAuthHeader(string authHeader) { var token = authHeader.Replace("Bearer ", ""); var handler = new JwtSecurityTokenHandler(); @@ -18,14 +18,19 @@ public static Guid GetUserIdFromAuthHeader(string authHeader) var payload = jsonToken.Payload; if (payload.TryGetValue("sub", out var userIdValue)) { - if (Guid.TryParse(userIdValue.ToString(), out Guid userId)) + if (userIdValue == null) { - return userId; - } - else - { - throw new ArgumentException("sub is not in a valid Guid format"); + throw new ArgumentException("sub has not a valid value"); } + return userIdValue.ToString()!; + //if (Guid.TryParse(userIdValue.ToString(), out Guid userId)) + //{ + // return userId; + //} + //else + //{ + // throw new ArgumentException("sub is not in a valid Guid format"); + //} } else { From 167d3a01d0c10d20eedf0c4d42f154073b2a29a7 Mon Sep 17 00:00:00 2001 From: superjekk Date: Tue, 9 Dec 2025 12:35:10 -0500 Subject: [PATCH 10/18] Changement du type ID d'un utilisateur MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Puisque l'identifiant d'un utilisateur n'utilise plus la forme GUID à cause du fournisseur d'identité, l'entièreté du code touchant à des utilisateurs doit être modifié --- core/Controllers/EventsController.cs | 2 +- core/Controllers/OrganizersController.cs | 4 ++-- core/Data/Entities/Publication.cs | 4 ++-- core/Data/Entities/Subscription.cs | 4 ++-- core/Data/Entities/User.cs | 17 +++++++++++++++-- core/Data/Requests/ModeratorRequestDTO.cs | 2 +- core/Data/Requests/SubscriptionRequestDTO.cs | 2 +- core/Data/Requests/UserRequestDTO.cs | 4 ++-- core/Data/Responses/ModeratorRequestDTO.cs | 2 +- core/Data/Responses/UserResponseDTO.cs | 2 +- core/Policies/IsModerator.cs | 5 +++-- core/Policies/OrganizerIsActive.cs | 5 +++-- .../Abstractions/IModeratorRepository.cs | 3 ++- .../Abstractions/IOrganizerRepository.cs | 3 ++- .../Abstractions/ISubscriptionRepository.cs | 2 +- .../Repositories/Abstractions/ITagRepository.cs | 2 +- .../Abstractions/IUserRepository.cs | 12 ++++++++++++ core/Repositories/ModeratorRepository.cs | 4 ++-- core/Repositories/OrganizerRepository.cs | 4 ++-- core/Repositories/SubscriptionRepository.cs | 2 +- core/Repositories/TagRepository.cs | 2 +- .../Services/Abstractions/IDraftEventService.cs | 4 ++-- core/Services/Abstractions/IEventService.cs | 10 +++++----- core/Services/Abstractions/IUserService.cs | 12 ++++++------ core/Services/DraftEventService.cs | 4 ++-- core/Services/EventService.cs | 14 +++++++------- core/Services/UserService.cs | 14 +++++++------- 27 files changed, 87 insertions(+), 58 deletions(-) create mode 100644 core/Repositories/Abstractions/IUserRepository.cs diff --git a/core/Controllers/EventsController.cs b/core/Controllers/EventsController.cs index e44944e..f7ab8fe 100644 --- a/core/Controllers/EventsController.cs +++ b/core/Controllers/EventsController.cs @@ -46,7 +46,7 @@ public class EventsController( public ActionResult> GetEvents( [FromQuery] DateTime? startDate, [FromQuery] DateTime? endDate, - [FromQuery] Guid? organizerId, + [FromQuery] string? organizerId, [FromQuery] string? title, [FromQuery] IEnumerable? activityAreas, [FromQuery] IEnumerable? tags, diff --git a/core/Controllers/OrganizersController.cs b/core/Controllers/OrganizersController.cs index 97e3e47..2f8837d 100644 --- a/core/Controllers/OrganizersController.cs +++ b/core/Controllers/OrganizersController.cs @@ -47,7 +47,7 @@ public class ModeratorUserController( /// [AllowAnonymous] [HttpGet("{organizerId}")] - public IActionResult GetOrganizer(Guid organizerId) + public IActionResult GetOrganizer(string organizerId) { var user = userService.GetUser(organizerId); return user.Type == "Organizer" ? @@ -129,7 +129,7 @@ public IActionResult GetUsers(string? search, OrganizerAccountActiveFilter filte /// pass a reason for the toggle active change, will be send by email /// [HttpPatch("{organizerId}/toggle")] - public async Task ToggleOrganizer(Guid organizerId, [FromQuery] string? reason) + public async Task ToggleOrganizer(string organizerId, [FromQuery] string? reason) { var success = userService.ToggleUserActiveState(organizerId); var organizer = userService.GetUser(organizerId); diff --git a/core/Data/Entities/Publication.cs b/core/Data/Entities/Publication.cs index 33c13fd..d102221 100644 --- a/core/Data/Entities/Publication.cs +++ b/core/Data/Entities/Publication.cs @@ -36,9 +36,9 @@ public partial class Publication public bool HasBeenReported { get; set; } = false; - public Guid? ModeratorId { get; set; } + public string? ModeratorId { get; set; } - public Guid OrganizerId { get; set; } + public string OrganizerId { get; set; } = null!; public DateTime CreatedAt { get; set; } diff --git a/core/Data/Entities/Subscription.cs b/core/Data/Entities/Subscription.cs index 93a804b..39a6903 100644 --- a/core/Data/Entities/Subscription.cs +++ b/core/Data/Entities/Subscription.cs @@ -9,8 +9,8 @@ namespace api.core.data.entities; public partial class Subscription : BaseEntity { public string Email { get; set; } = null!; - - public Guid OrganizerId { get; set; } + + public string OrganizerId { get; set; } = null!; public string SubscriptionToken { get; set; } = null!; diff --git a/core/Data/Entities/User.cs b/core/Data/Entities/User.cs index bfc6fb5..e395769 100644 --- a/core/Data/Entities/User.cs +++ b/core/Data/Entities/User.cs @@ -1,8 +1,21 @@ -namespace api.core.Data.Entities; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; -public class User : BaseEntity +namespace api.core.Data.Entities; + +public class User { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + public string Id { get; set; } = null!; + public string Email { get; set; } = null!; public Guid? ActivityAreaId { get; set; } + + public DateTime CreatedAt { get; set; } + + public DateTime UpdatedAt { get; set; } + + public DateTime? DeletedAt { get; set; } } diff --git a/core/Data/Requests/ModeratorRequestDTO.cs b/core/Data/Requests/ModeratorRequestDTO.cs index 5c376ca..4c69bd3 100644 --- a/core/Data/Requests/ModeratorRequestDTO.cs +++ b/core/Data/Requests/ModeratorRequestDTO.cs @@ -5,7 +5,7 @@ public class ModeratorCreateRequestDTO /// /// This id when passed needs to be already created in supabase. /// - public required Guid Id { get; set; } + public required string Id { get; set; } /// /// A email that will be bound to the moderator, make sure that this email match the diff --git a/core/Data/Requests/SubscriptionRequestDTO.cs b/core/Data/Requests/SubscriptionRequestDTO.cs index 17935b3..6c46216 100644 --- a/core/Data/Requests/SubscriptionRequestDTO.cs +++ b/core/Data/Requests/SubscriptionRequestDTO.cs @@ -4,7 +4,7 @@ public class SubscribeRequestDTO { public required string Email { get; set; } - public required Guid OrganizerId { get; set; } + public required string OrganizerId { get; set; } } diff --git a/core/Data/Requests/UserRequestDTO.cs b/core/Data/Requests/UserRequestDTO.cs index 7f4e988..de0f2b2 100644 --- a/core/Data/Requests/UserRequestDTO.cs +++ b/core/Data/Requests/UserRequestDTO.cs @@ -2,6 +2,8 @@ public class UserCreateDTO { + public required string Id { get; set; } = null!; + public required string Email { get; set; } public string? Organization { get; set; } = null!; @@ -11,8 +13,6 @@ public class UserCreateDTO public class UserUpdateDTO : UserCreateDTO { - public Guid Id { get; set; } - public bool? HasLoggedIn { get; set; } public string? ProfileDescription { get; set; } = null!; diff --git a/core/Data/Responses/ModeratorRequestDTO.cs b/core/Data/Responses/ModeratorRequestDTO.cs index 0e1b09f..9d937ed 100644 --- a/core/Data/Responses/ModeratorRequestDTO.cs +++ b/core/Data/Responses/ModeratorRequestDTO.cs @@ -2,7 +2,7 @@ public class ModeratorResponseDTO { - public required Guid Id { get; set; } + public required string Id { get; set; } public required string Email { get; set; } public DateTime? CreatedAt { get; set; } public DateTime? UpdatedAt { get; set; } diff --git a/core/Data/Responses/UserResponseDTO.cs b/core/Data/Responses/UserResponseDTO.cs index 41f4eff..3cc58ea 100644 --- a/core/Data/Responses/UserResponseDTO.cs +++ b/core/Data/Responses/UserResponseDTO.cs @@ -4,7 +4,7 @@ namespace api.core.Data.Responses; public class UserResponseDTO { - public Guid Id { get; set; } + public string Id { get; set; } public string Name { get; set; } = null!; diff --git a/core/Policies/IsModerator.cs b/core/Policies/IsModerator.cs index 430eff9..9dc1784 100644 --- a/core/Policies/IsModerator.cs +++ b/core/Policies/IsModerator.cs @@ -13,10 +13,11 @@ public class IsModeratorHandler(IUserService userService) : AuthorizationHandler { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsModeratorRequirement requirement) { + // TODO : Remplacer cette politique autrement var identifierClaim = context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; - var userId = Guid.Parse(identifierClaim ?? throw new UnauthorizedException()); + //var userId = Guid.Parse(identifierClaim ?? throw new UnauthorizedException()); - var user = userService.GetUser(userId); + var user = userService.GetUser(identifierClaim); if (user != null && user.Type != "Moderator") throw new UnauthorizedException(); else diff --git a/core/Policies/OrganizerIsActive.cs b/core/Policies/OrganizerIsActive.cs index 542c103..39e618b 100644 --- a/core/Policies/OrganizerIsActive.cs +++ b/core/Policies/OrganizerIsActive.cs @@ -12,10 +12,11 @@ public class OrganizerIsActiveHandler(IUserService userService) : AuthorizationH { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OrganizerIsActiveRequirement requirement) { + // TODO : Remplacer cette politique autrement var identifierClaim = context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; - var userId = Guid.Parse(identifierClaim ?? throw new UnauthorizedException()); + //var userId = Guid.Parse(identifierClaim ?? throw new UnauthorizedException()); - var user = userService.GetUser(userId); + var user = userService.GetUser(identifierClaim); if (user.Type != "Organizer" || !user.IsActive) throw new UnauthorizedException(); else diff --git a/core/Repositories/Abstractions/IModeratorRepository.cs b/core/Repositories/Abstractions/IModeratorRepository.cs index 5a24b08..bac4dea 100644 --- a/core/Repositories/Abstractions/IModeratorRepository.cs +++ b/core/Repositories/Abstractions/IModeratorRepository.cs @@ -1,7 +1,8 @@ using api.core.data.entities; +using api.core.Repositories.Abstractions; namespace api.core.repositories.abstractions; -public interface IModeratorRepository : IRepository +public interface IModeratorRepository : IUserRepository { } diff --git a/core/Repositories/Abstractions/IOrganizerRepository.cs b/core/Repositories/Abstractions/IOrganizerRepository.cs index a44be31..2872c81 100644 --- a/core/Repositories/Abstractions/IOrganizerRepository.cs +++ b/core/Repositories/Abstractions/IOrganizerRepository.cs @@ -1,8 +1,9 @@ using api.core.data.entities; using api.core.Data.requests; +using api.core.Repositories.Abstractions; namespace api.core.repositories.abstractions; -public interface IOrganizerRepository : IRepository +public interface IOrganizerRepository : IUserRepository { } diff --git a/core/Repositories/Abstractions/ISubscriptionRepository.cs b/core/Repositories/Abstractions/ISubscriptionRepository.cs index bdc18e1..023a712 100644 --- a/core/Repositories/Abstractions/ISubscriptionRepository.cs +++ b/core/Repositories/Abstractions/ISubscriptionRepository.cs @@ -4,5 +4,5 @@ namespace api.core.repositories.abstractions; public interface ISubscriptionRepository : IRepository { - public bool IsEntryExists(Guid organizerId, string email); + public bool IsEntryExists(string organizerId, string email); } diff --git a/core/Repositories/Abstractions/ITagRepository.cs b/core/Repositories/Abstractions/ITagRepository.cs index 40b5d37..e751d3f 100644 --- a/core/Repositories/Abstractions/ITagRepository.cs +++ b/core/Repositories/Abstractions/ITagRepository.cs @@ -5,5 +5,5 @@ namespace api.core.repositories.abstractions; public interface ITagRepository : IRepository { - public IEnumerable GetInterestFieldsForOrganizer(Guid organizerId, int take = 3); + public IEnumerable GetInterestFieldsForOrganizer(string organizerId, int take = 3); } diff --git a/core/Repositories/Abstractions/IUserRepository.cs b/core/Repositories/Abstractions/IUserRepository.cs new file mode 100644 index 0000000..930e839 --- /dev/null +++ b/core/Repositories/Abstractions/IUserRepository.cs @@ -0,0 +1,12 @@ +using api.core.Data.Entities; + +namespace api.core.Repositories.Abstractions; + +public interface IUserRepository where T : User +{ + public T Add(T entity); + public bool Delete(T entity); + public T? Get(string id); + public IQueryable GetAll(); + public bool Update(string id, T entity); +} diff --git a/core/Repositories/ModeratorRepository.cs b/core/Repositories/ModeratorRepository.cs index f7f3d72..aa79672 100644 --- a/core/Repositories/ModeratorRepository.cs +++ b/core/Repositories/ModeratorRepository.cs @@ -23,7 +23,7 @@ public bool Delete(Moderator entity) throw new NotImplementedException(); } - public Moderator? Get(Guid id) + public Moderator? Get(string id) { var entity = context.Moderators.Find(id); if (entity != null && entity.DeletedAt == null) @@ -38,7 +38,7 @@ public IQueryable GetAll() throw new NotImplementedException(); } - public bool Update(Guid id, Moderator entity) + public bool Update(string id, Moderator entity) { var existingEntity = Get(id); diff --git a/core/Repositories/OrganizerRepository.cs b/core/Repositories/OrganizerRepository.cs index 9daf67a..9e57297 100644 --- a/core/Repositories/OrganizerRepository.cs +++ b/core/Repositories/OrganizerRepository.cs @@ -25,7 +25,7 @@ public bool Delete(Organizer entity) throw new NotImplementedException(); } - public Organizer? Get(Guid id) + public Organizer? Get(string id) { var entity = context.Organizers .Include(x => x.ActivityArea) @@ -43,7 +43,7 @@ public IQueryable GetAll() .Include(x => x.ActivityArea); } - public bool Update(Guid id, Organizer entity) + public bool Update(string id, Organizer entity) { var existingEntity = Get(id); diff --git a/core/Repositories/SubscriptionRepository.cs b/core/Repositories/SubscriptionRepository.cs index 9c9a319..73c9b01 100644 --- a/core/Repositories/SubscriptionRepository.cs +++ b/core/Repositories/SubscriptionRepository.cs @@ -72,7 +72,7 @@ public bool Update(Guid id, Subscription entity) return false; } - public bool IsEntryExists(Guid organizerId, string email) + public bool IsEntryExists(string organizerId, string email) { return context.Subscriptions .Any(x => x.OrganizerId == organizerId && x.Email == email); diff --git a/core/Repositories/TagRepository.cs b/core/Repositories/TagRepository.cs index 488df81..9b6b6e5 100644 --- a/core/Repositories/TagRepository.cs +++ b/core/Repositories/TagRepository.cs @@ -49,7 +49,7 @@ public IQueryable GetAll() return context.Tags; } - public IEnumerable GetInterestFieldsForOrganizer(Guid organizerId, int take = 3) + public IEnumerable GetInterestFieldsForOrganizer(string organizerId, int take = 3) { return context.Publications .Include(x => x.Tags) diff --git a/core/Services/Abstractions/IDraftEventService.cs b/core/Services/Abstractions/IDraftEventService.cs index 549dda7..33e3e6c 100644 --- a/core/Services/Abstractions/IDraftEventService.cs +++ b/core/Services/Abstractions/IDraftEventService.cs @@ -8,7 +8,7 @@ namespace api.core.services.abstractions; public interface IDraftEventService { - public EventResponseDTO AddDraftEvent(Guid userId, DraftEventRequestDTO request); + public EventResponseDTO AddDraftEvent(string userId, DraftEventRequestDTO request); - public bool UpdateDraftEvent(Guid userId, Guid eventId, DraftEventRequestDTO request); + public bool UpdateDraftEvent(string userId, Guid eventId, DraftEventRequestDTO request); } diff --git a/core/Services/Abstractions/IEventService.cs b/core/Services/Abstractions/IEventService.cs index b24e023..d0ea487 100644 --- a/core/Services/Abstractions/IEventService.cs +++ b/core/Services/Abstractions/IEventService.cs @@ -6,17 +6,17 @@ namespace api.core.services.abstractions; public interface IEventService { - public IEnumerable GetEvents(DateTime? startDate, DateTime? endDate, IEnumerable? activityAreas, IEnumerable? tags, Guid? organizerId, string? title, State state, string orderBy = "EventStartDate", bool desc = false, bool ignorePublicationDate = false); + public IEnumerable GetEvents(DateTime? startDate, DateTime? endDate, IEnumerable? activityAreas, IEnumerable? tags, string? organizerId, string? title, State state, string orderBy = "EventStartDate", bool desc = false, bool ignorePublicationDate = false); public EventResponseDTO GetEvent(Guid id); - public EventResponseDTO AddEvent(Guid userId, EventCreationRequestDTO request); + public EventResponseDTO AddEvent(string userId, EventCreationRequestDTO request); - public bool DeleteEvent(Guid userId, Guid eventId); + public bool DeleteEvent(string userId, Guid eventId); - public bool UpdateEvent(Guid userId, Guid eventId, EventUpdateRequestDTO request); + public bool UpdateEvent(string userId, Guid eventId, EventUpdateRequestDTO request); - public bool UpdateEventState(Guid userId, Guid eventId, State state, string? reason); + public bool UpdateEventState(string userId, Guid eventId, State state, string? reason); public bool UpdateEventReportCount(Guid eventId); diff --git a/core/Services/Abstractions/IUserService.cs b/core/Services/Abstractions/IUserService.cs index be4098c..a729787 100644 --- a/core/Services/Abstractions/IUserService.cs +++ b/core/Services/Abstractions/IUserService.cs @@ -8,17 +8,17 @@ namespace api.core.services.abstractions; public interface IUserService { - public UserResponseDTO AddOrganizer(Guid id, UserCreateDTO organizerDto); + public UserResponseDTO AddOrganizer(string id, UserCreateDTO organizerDto); - public UserResponseDTO GetUser(Guid id); + public UserResponseDTO GetUser(string id); - public string GetUserAvatarUrl(Guid id); + public string GetUserAvatarUrl(string id); public IEnumerable GetUsers(string? search, OrganizerAccountActiveFilter activeFilter, out int count); - public bool UpdateUser(Guid id, UserUpdateDTO dto); + public bool UpdateUser(string id, UserUpdateDTO dto); - public bool ToggleUserActiveState(Guid id); + public bool ToggleUserActiveState(string id); - public string UpdateUserAvatar(Guid id, IFormFile avatarFile); + public string UpdateUserAvatar(string id, IFormFile avatarFile); } diff --git a/core/Services/DraftEventService.cs b/core/Services/DraftEventService.cs index 73dba04..87d1294 100644 --- a/core/Services/DraftEventService.cs +++ b/core/Services/DraftEventService.cs @@ -27,7 +27,7 @@ public class DraftEventService( IFileShareService fileShareService, IImageService imageService) : IDraftEventService { - public EventResponseDTO AddDraftEvent(Guid userId, DraftEventRequestDTO request) + public EventResponseDTO AddDraftEvent(string userId, DraftEventRequestDTO request) { var organizer = orgRepo.Get(userId) ?? throw new UnauthorizedException(); @@ -67,7 +67,7 @@ public EventResponseDTO AddDraftEvent(Guid userId, DraftEventRequestDTO request) return EventResponseDTO.Map(inserted); } - public bool UpdateDraftEvent(Guid userId, Guid eventId, DraftEventRequestDTO request) + public bool UpdateDraftEvent(string userId, Guid eventId, DraftEventRequestDTO request) { _ = orgRepo.Get(userId) ?? throw new UnauthorizedException(); diff --git a/core/Services/EventService.cs b/core/Services/EventService.cs index 4153dff..f8690a3 100644 --- a/core/Services/EventService.cs +++ b/core/Services/EventService.cs @@ -35,7 +35,7 @@ public IEnumerable GetEvents( DateTime? endDate, IEnumerable? activityAreas, IEnumerable? tags, - Guid? organizerId, + string? organizerId, string? title, State state, string orderBy = "EventStartDate", @@ -67,7 +67,7 @@ public EventResponseDTO GetEvent(Guid id) return EventResponseDTO.Map(evnt!); } - public EventResponseDTO AddEvent(Guid userId, EventCreationRequestDTO request) + public EventResponseDTO AddEvent(string userId, EventCreationRequestDTO request) { var organizer = orgRepo.Get(userId) ?? throw new UnauthorizedException(); @@ -110,7 +110,7 @@ public EventResponseDTO AddEvent(Guid userId, EventCreationRequestDTO request) return EventResponseDTO.Map(inserted); } - public EventResponseDTO AddDraftEvent(Guid userId, DraftEventRequestDTO request) + public EventResponseDTO AddDraftEvent(string userId, DraftEventRequestDTO request) { var organizer = orgRepo.Get(userId) ?? throw new UnauthorizedException(); @@ -155,7 +155,7 @@ public EventResponseDTO AddDraftEvent(Guid userId, DraftEventRequestDTO request) return EventResponseDTO.Map(inserted); } - public bool DeleteEvent(Guid userId, Guid eventId) + public bool DeleteEvent(string userId, Guid eventId) { var eventToDelete = evntRepo.Get(eventId); NotFoundException.ThrowIfNull(eventToDelete); @@ -165,7 +165,7 @@ public bool DeleteEvent(Guid userId, Guid eventId) throw new UnauthorizedException(); } - public bool UpdateEvent(Guid userId, Guid eventId, EventUpdateRequestDTO request) + public bool UpdateEvent(string userId, Guid eventId, EventUpdateRequestDTO request) { _ = orgRepo.Get(userId) ?? throw new UnauthorizedException(); @@ -204,7 +204,7 @@ public bool UpdateEvent(Guid userId, Guid eventId, EventUpdateRequestDTO request return evntRepo.Update(eventId, evnt); } - public bool UpdateEventState(Guid userId, Guid eventId, State state, string? reason) + public bool UpdateEventState(string userId, Guid eventId, State state, string? reason) { var moderator = moderatorRepo.Get(userId) ?? throw new UnauthorizedException(); var evnt = evntRepo.Get(eventId); @@ -265,7 +265,7 @@ private void SendEmailStatusChange(Event evnt, string? reason) EmailsUtils.StatusChangeTemplate); } - private static bool CanPerformAction(Guid userId, Event evnt) + private static bool CanPerformAction(string userId, Event evnt) { return (evnt!.Publication.Moderator != null && evnt.Publication.Moderator.Id == userId) || (evnt!.Publication.Organizer != null && evnt.Publication.Organizer.Id == userId); diff --git a/core/Services/UserService.cs b/core/Services/UserService.cs index 0068832..6638b49 100644 --- a/core/Services/UserService.cs +++ b/core/Services/UserService.cs @@ -22,7 +22,7 @@ public class UserService( { private const string AVATAR_FILE_NAME = "avatar.webp"; - public UserResponseDTO AddOrganizer(Guid id, UserCreateDTO organizerDto) + public UserResponseDTO AddOrganizer(string id, UserCreateDTO organizerDto) { if (organizerDto.ActivityAreaId != null) { @@ -50,7 +50,7 @@ public UserResponseDTO AddOrganizer(Guid id, UserCreateDTO organizerDto) return user; } - public UserResponseDTO GetUser(Guid id) + public UserResponseDTO GetUser(string id) { UserResponseDTO? userRes = null; var organizer = organizerRepository.Get(id); @@ -72,7 +72,7 @@ public UserResponseDTO GetUser(Guid id) return userRes; } - public string GetUserAvatarUrl(Guid id) + public string GetUserAvatarUrl(string id) { var avatarUri = fileShareService.FileGetDownloadUri($"{id}/{AVATAR_FILE_NAME}"); return avatarUri.ToString(); @@ -93,7 +93,7 @@ public IEnumerable GetUsers(string? search, OrganizerAccountAct return organizers.Select(UserResponseDTO.Map); } - public bool ToggleUserActiveState(Guid id) + public bool ToggleUserActiveState(string id) { EnsureIsOrganizer(id); @@ -102,7 +102,7 @@ public bool ToggleUserActiveState(Guid id) return organizerRepository.Update(id, user); } - private void EnsureIsOrganizer(Guid id) + private void EnsureIsOrganizer(string id) { var user = GetUser(id); @@ -110,7 +110,7 @@ private void EnsureIsOrganizer(Guid id) throw new Exception("Moderators cannot be disabled"); } - public bool UpdateUser(Guid id, UserUpdateDTO dto) + public bool UpdateUser(string id, UserUpdateDTO dto) { var user = GetUser(id); @@ -153,7 +153,7 @@ public bool UpdateUser(Guid id, UserUpdateDTO dto) }; } - public string UpdateUserAvatar(Guid id, IFormFile avatarFile) + public string UpdateUserAvatar(string id, IFormFile avatarFile) { _ = GetUser(id); var userId = id.ToString(); From 11229f5859d12d86b2e5d50f323fbc2511b3e77c Mon Sep 17 00:00:00 2001 From: superjekk Date: Thu, 11 Dec 2025 14:16:33 -0500 Subject: [PATCH 11/18] =?UTF-8?q?FIX=20:=20Adaptation=20des=20tests=20unit?= =?UTF-8?q?aires=20et=20r=C3=A9paration=20de=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Des méthodes d'un service étaient brisées. Elles ont donc été réparées en même temps que les tests unitaires ont été adaptés afin de respecter la nouvelle manière d'appliquer la gestion d'identité --- core/Program.cs | 256 +++++++++--------- core/Services/EventService.cs | 4 +- tests/Tests/Services/EventServiceTests.cs | 30 +- .../Services/NotificationServiceTests.cs | 29 +- tests/Tests/Services/UserServiceTests.cs | 26 +- 5 files changed, 176 insertions(+), 169 deletions(-) diff --git a/core/Program.cs b/core/Program.cs index 298c46a..b8bb114 100644 --- a/core/Program.cs +++ b/core/Program.cs @@ -1,42 +1,42 @@ -using System.Reflection; -using System.Text; - -using api.core.data; -using api.core.Extensions; -using api.core.Misc; +using System.Reflection; +using System.Text; + +using api.core.data; +using api.core.Extensions; +using api.core.Misc; using api.emails; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Tokens; -using Microsoft.OpenApi.Models; - - -IdentityModelEventSource.ShowPII = true; - -var builder = WebApplication.CreateBuilder(args); - -// Environments setup -string connectionString = null!; -string? redisConnString = null!; - -connectionString = - Environment.GetEnvironmentVariable("CONNECTION_STRING") - ?? throw new Exception("CONNECTION_STRING is not set"); -redisConnString = Environment.GetEnvironmentVariable("REDIS_CONNECTION_STRING"); - -builder.Configuration.AddEnvironmentVariables(); - +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; + + +IdentityModelEventSource.ShowPII = true; + +var builder = WebApplication.CreateBuilder(args); + +// Environments setup +string connectionString = null!; +string? redisConnString = null!; + +connectionString = + Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? throw new Exception("CONNECTION_STRING is not set"); +redisConnString = Environment.GetEnvironmentVariable("REDIS_CONNECTION_STRING"); + +builder.Configuration.AddEnvironmentVariables(); + builder.Services.AddDbContext(opt => opt.UseNpgsql(connectionString)); - -var key = Encoding.ASCII.GetBytes(Environment.GetEnvironmentVariable("OPENID_CLIENT_SECRET") ?? ""); -builder.Services - .AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + +var key = Encoding.ASCII.GetBytes(Environment.GetEnvironmentVariable("OPENID_CLIENT_SECRET") ?? ""); +builder.Services + .AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) //.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => //{ @@ -106,96 +106,96 @@ // //}; // }); -builder.Services.SetupScheduler(); - -if (string.IsNullOrEmpty(redisConnString)) -{ - builder.Services.AddStackExchangeRedisOutputCache(options => - { - options.Configuration = redisConnString; - }); -} - -builder.Services.AddOutputCache(options => -{ - options.AddBasePolicy(builder => builder.Cache()); -}); - -// Errors handling -builder.Services.AddExceptionHandler(); -builder.Services.AddProblemDetails(); - -// Endpoints -builder.Services.AddControllers(); - -builder.Services.AddHealthChecks().AddNpgSql(connectionString); - -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(options => -{ - options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() - { - Name = "Authorization", - Type = SecuritySchemeType.Http, - Scheme = "Bearer", - BearerFormat = "JWT", - In = ParameterLocation.Header, - Description = "JWT Authorization header using the Bearer scheme. " + - "\r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.", - OpenIdConnectUrl = new Uri(Environment.GetEnvironmentVariable("OPENID_ISSUER")!) - }); - - options.AddSecurityRequirement(new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Bearer" - } - }, - Array.Empty() - } - }); - options.UseInlineDefinitionsForEnums(); - - var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; - var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); - options.IncludeXmlComments(xmlPath); -}); - -builder.Services.AddEmailService(builder.Configuration); - -builder.Services.AddDependencyInjection(); -builder.Services.AddPolicies(); - -builder.Services.AddRateLimiters(); - -var app = builder.Build(); - -await using var scope = app.Services.CreateAsyncScope(); -await using var db = scope.ServiceProvider.GetService(); -await db!.Database.MigrateAsync(); - -app.UseSwagger(); -app.UseSwaggerUI(); - -app.UseExceptionMiddleware(); - -// app.UseHttpsRedirection(); - -app.UseAuthentication(); -app.UseAuthorization(); - -app.MapHealthChecks("/health"); - -await app.Services.AddSchedulerAsync(); - -if (redisConnString != null) - app.UseOutputCache(); - -app.MapControllers(); - -app.Run(); +builder.Services.SetupScheduler(); + +if (string.IsNullOrEmpty(redisConnString)) +{ + builder.Services.AddStackExchangeRedisOutputCache(options => + { + options.Configuration = redisConnString; + }); +} + +builder.Services.AddOutputCache(options => +{ + options.AddBasePolicy(builder => builder.Cache()); +}); + +// Errors handling +builder.Services.AddExceptionHandler(); +builder.Services.AddProblemDetails(); + +// Endpoints +builder.Services.AddControllers(); + +builder.Services.AddHealthChecks().AddNpgSql(connectionString); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(options => +{ + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() + { + Name = "Authorization", + Type = SecuritySchemeType.Http, + Scheme = "Bearer", + BearerFormat = "JWT", + In = ParameterLocation.Header, + Description = "JWT Authorization header using the Bearer scheme. " + + "\r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.", + OpenIdConnectUrl = new Uri(Environment.GetEnvironmentVariable("OPENID_ISSUER")!) + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } + }); + options.UseInlineDefinitionsForEnums(); + + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + options.IncludeXmlComments(xmlPath); +}); + +builder.Services.AddEmailService(builder.Configuration); + +builder.Services.AddDependencyInjection(); +builder.Services.AddPolicies(); + +builder.Services.AddRateLimiters(); + +var app = builder.Build(); + +await using var scope = app.Services.CreateAsyncScope(); +await using var db = scope.ServiceProvider.GetService(); +await db!.Database.MigrateAsync(); + +app.UseSwagger(); +app.UseSwaggerUI(); + +app.UseExceptionMiddleware(); + +// app.UseHttpsRedirection(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapHealthChecks("/health"); + +await app.Services.AddSchedulerAsync(); + +if (redisConnString != null) + app.UseOutputCache(); + +app.MapControllers(); + +app.Run(); diff --git a/core/Services/EventService.cs b/core/Services/EventService.cs index f8690a3..73eda5b 100644 --- a/core/Services/EventService.cs +++ b/core/Services/EventService.cs @@ -52,8 +52,8 @@ public IEnumerable GetEvents( (state.HasFlag(e.Publication.State)) && (organizerId == null || e.Publication.OrganizerId == organizerId) && (title == null || (e.Publication.Title != null && e.Publication.Title.ToLower().Contains(title.ToLower()))) && - (tags.Count() < 1 || e.Publication.Tags.Any(t => tags!.Any(tt => t.Id == tt))) && - (activityAreas.Count() < 1 || activityAreas!.Any(aa => aa == e.Publication.Organizer.ActivityAreaId))) + (tags == null || tags.Count() < 1 || e.Publication.Tags.Any(t => tags!.Any(tt => t.Id == tt))) && + (activityAreas == null || activityAreas.Count() < 1 || activityAreas!.Any(aa => aa == e.Publication.Organizer.ActivityAreaId))) .AsQueryable() .OrderBy(orderBy, desc) .Select(EventResponseDTO.Map); diff --git a/tests/Tests/Services/EventServiceTests.cs b/tests/Tests/Services/EventServiceTests.cs index 27eb89b..808e6c5 100644 --- a/tests/Tests/Services/EventServiceTests.cs +++ b/tests/Tests/Services/EventServiceTests.cs @@ -48,7 +48,7 @@ public class EventServiceTests ], Organizer = new Organizer { - Id = Guid.NewGuid(), + Id = Guid.NewGuid().ToString(), ActivityAreaId = ActivityAreaClubId, ActivityArea = new ActivityArea { @@ -59,7 +59,7 @@ public class EventServiceTests }, Moderator = new Moderator { - Id = Guid.NewGuid(), + Id = Guid.NewGuid().ToString(), }, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now @@ -87,7 +87,7 @@ public class EventServiceTests ], Organizer = new Organizer { - Id = Guid.NewGuid(), + Id = Guid.NewGuid().ToString(), ActivityAreaId = ActivityAreaSchoolId, ActivityArea = new ActivityArea { @@ -115,7 +115,7 @@ public class EventServiceTests Tags = [], Organizer = new Organizer { - Id = Guid.NewGuid(), + Id = Guid.NewGuid().ToString(), ActivityAreaId = ActivityAreaSchoolId, ActivityArea = new ActivityArea { @@ -150,7 +150,7 @@ public class EventServiceTests ], Organizer = new Organizer { - Id = Guid.NewGuid(), + Id = Guid.NewGuid().ToString(), ActivityAreaId = ActivityAreaSchoolId, ActivityArea = new ActivityArea { @@ -335,11 +335,11 @@ public void GetEvent_ShouldReturnEvent() public void AddEvents_ShouldThrowAnExceptionWhenOrganizerIsUnknown() { // Arrange - _mockOrganizerRepository.Setup(repo => repo.Get(It.IsAny())).Returns((Organizer?)null); + _mockOrganizerRepository.Setup(repo => repo.Get(It.IsAny())).Returns((Organizer?)null); // Act _eventService.Invoking(s => - s.AddEvent(Guid.Empty, new EventCreationRequestDTO())) + s.AddEvent("", new EventCreationRequestDTO())) .Should().Throw(); } @@ -371,7 +371,7 @@ public void DeleteEvent_ShouldThrowNotFoundException_WhenEventDoesNotExist() _mockEventRepository.Setup(repo => repo.Get(eventId)).Returns((Event?)null); // Act - Action act = () => _eventService.DeleteEvent(userId, eventId); + Action act = () => _eventService.DeleteEvent("", eventId); // Assert act.Should().Throw>(); @@ -382,7 +382,7 @@ public void DeleteEvent_ShouldThrowNotFoundException_WhenEventDoesNotExist() public void DeleteEvent_ShouldThrowUnauthorizedException_WhenUserIsNotAuthorized() { // Arrange - var unauthorizedUserId = Guid.NewGuid(); + var unauthorizedUserId = "unauthorized-user"; var eventId = _events.First().Id; _mockEventRepository.Setup(repo => repo.Get(eventId)).Returns(_events.First()); @@ -420,7 +420,7 @@ public void UpdateEvent_ShouldReturnTrue_WhenEventIsUpdatedSuccessfully() _mockEventRepository.Setup(repo => repo.Get(eventId)).Returns(_events.First()); _mockEventRepository.Setup(repo => repo.Update(eventId, It.IsAny())).Returns(true); - _mockOrganizerRepository.Setup(repo => repo.Get(It.IsAny())).Returns(new Organizer { Id = userId }); + _mockOrganizerRepository.Setup(repo => repo.Get(It.IsAny())).Returns(new Organizer { Id = userId }); _eventService = new EventService( _mockConfig.Object, @@ -445,7 +445,7 @@ public void UpdateEvent_ShouldReturnTrue_WhenEventIsUpdatedSuccessfully() public void UpdateEvent_ShouldThrowUnauthorizedException_WhenUserIsNotAuthorized() { // Arrange - var unauthorizedUserId = Guid.NewGuid(); + var unauthorizedUserId = "unauthorized-user"; var eventId = _events.First().Id; var request = new EventUpdateRequestDTO @@ -488,7 +488,7 @@ public void UpdateEventState_ShouldReturnTrue_WhenStateIsUpdatedSuccessfullyByMo _mockEventRepository.Setup(repo => repo.Get(eventId)).Returns(eventToUpdate); _mockEventRepository.Setup(repo => repo.Update(eventId, It.IsAny())).Returns(true); - _mockModeratorRepository.Setup(repo => repo.Get(It.IsAny())).Returns(new Moderator { Id = userId }); + _mockModeratorRepository.Setup(repo => repo.Get(It.IsAny())).Returns(new Moderator { Id = userId }); _mockEmailService.Setup(service => service.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); // Act @@ -513,7 +513,7 @@ public void UpdateEventState_ShouldHaveStatePublished_WhenStateIsUpdatedWithAppr _mockEventRepository.Setup(repo => repo.Get(eventId)).Returns(eventToUpdate); _mockEventRepository.Setup(repo => repo.Update(eventId, It.IsAny())).Returns(true); - _mockModeratorRepository.Setup(repo => repo.Get(It.IsAny())).Returns(new Moderator { Id = userId }); + _mockModeratorRepository.Setup(repo => repo.Get(It.IsAny())).Returns(new Moderator { Id = userId }); _mockEmailService.Setup(service => service.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); // Act @@ -532,12 +532,12 @@ public void UpdateEventState_ShouldHaveStatePublished_WhenStateIsUpdatedWithAppr public void UpdateEventState_ShouldThrowUnauthorizedException_WhenUserIsNotAuthorized() { // Arrange - var unauthorizedUserId = Guid.NewGuid(); + var unauthorizedUserId = "unauthorized-user"; var eventId = _events.First().Id; var newState = State.Approved; var eventToUpdate = _events.First(); - eventToUpdate.Publication.ModeratorId = Guid.NewGuid(); + eventToUpdate.Publication.ModeratorId = "moderator-id"; _mockEventRepository.Setup(repo => repo.Get(eventId)).Returns(eventToUpdate); diff --git a/tests/Tests/Services/NotificationServiceTests.cs b/tests/Tests/Services/NotificationServiceTests.cs index 4b6dbbd..e6b1c19 100644 --- a/tests/Tests/Services/NotificationServiceTests.cs +++ b/tests/Tests/Services/NotificationServiceTests.cs @@ -53,7 +53,7 @@ public void BulkAddNotificationForPublication_ShouldAdd2NotifsForPub() { // Arrange var pubId = Guid.NewGuid(); - var orgId = Guid.NewGuid(); + var orgId = "org"; _mockEventRepository.Setup(x => x.GetAll()).Returns(new List { new Event @@ -131,8 +131,8 @@ public void BulkAddNotificationForPublication_ShouldAddNoneWhenSubscriptionOnOth { // Arrange var pubId = Guid.NewGuid(); - var orgId = Guid.NewGuid(); - var otherOrgId = Guid.NewGuid(); + var orgId = "org"; + var otherOrgId = "otherOrg"; _mockEventRepository.Setup(x => x.GetAll()).Returns(new List { new Event @@ -176,7 +176,8 @@ public void BulkAddNotificationForPublication_ShouldCleanIsSentNotif() { // Arrange var pubId = Guid.NewGuid(); - var orgId = Guid.NewGuid(); + var subId = Guid.NewGuid(); + var orgId = "org"; _mockEventRepository.Setup(x => x.GetAll()).Returns(new List { new Event @@ -193,7 +194,7 @@ public void BulkAddNotificationForPublication_ShouldCleanIsSentNotif() { new Subscription { - Id = Guid.NewGuid(), + Id = subId, OrganizerId = orgId, DeletedAt = null, CreatedAt = DateTime.UtcNow @@ -204,7 +205,7 @@ public void BulkAddNotificationForPublication_ShouldCleanIsSentNotif() new Notification { Id = Guid.NewGuid(), - SubscriptionId = orgId, + SubscriptionId = subId, PublicationId = pubId, IsSent = true, DeletedAt = null, @@ -228,18 +229,19 @@ public async Task SendNewsForRemainingPublication_ShouldSendNotificationWaiting( // Arrange var notifId = Guid.NewGuid(); var pubId = Guid.NewGuid(); - var orgId = Guid.NewGuid(); + var subId = Guid.NewGuid(); + var orgId = "org"; _mockNotifRepository.Setup(x => x.GetAll()).Returns(new List { new Notification { Id = notifId, - SubscriptionId = orgId, + SubscriptionId = subId, Subscription = new Subscription { - Id = orgId, + Id = subId, OrganizerId = orgId, - SubscriptionToken = "token" + SubscriptionToken = "token", }, PublicationId = pubId, Publication = new Publication @@ -284,16 +286,17 @@ public async Task SendNewsForRemainingPublication_ShouldReturn0Early() { // Arrange var pubId = Guid.NewGuid(); - var orgId = Guid.NewGuid(); + var subId = Guid.NewGuid(); + var orgId = "org"; _mockNotifRepository.Setup(x => x.GetAll()).Returns(new List { new Notification { Id = Guid.NewGuid(), - SubscriptionId = orgId, + SubscriptionId = subId, Subscription = new Subscription { - Id = orgId, + Id = subId, OrganizerId = orgId, SubscriptionToken = "token" }, diff --git a/tests/Tests/Services/UserServiceTests.cs b/tests/Tests/Services/UserServiceTests.cs index 0e24441..d4e5a64 100644 --- a/tests/Tests/Services/UserServiceTests.cs +++ b/tests/Tests/Services/UserServiceTests.cs @@ -51,7 +51,8 @@ public void AddOrganizer_ShouldReturnUserResponseDTO_WhenOrganizerIsAddedSuccess { Email = "john.doe@example.com", Organization = "ExampleOrg", - ActivityAreaId = actAreaModified + ActivityAreaId = actAreaModified, + Id = "1234" }; var activity = new ActivityArea @@ -74,7 +75,7 @@ public void AddOrganizer_ShouldReturnUserResponseDTO_WhenOrganizerIsAddedSuccess _organizerRepositoryMock.Setup(repo => repo.Add(It.IsAny())).Returns(organizer); // Act - var result = _userService.AddOrganizer(Guid.NewGuid(), organizerDto); + var result = _userService.AddOrganizer("1234", organizerDto); // Assert result.Should().NotBeNull(); @@ -89,7 +90,7 @@ public void AddOrganizer_ShouldReturnUserResponseDTO_WhenOrganizerIsAddedSuccess public void GetUser_ShouldReturnUserResponseDTO_WhenOrganizerIsFoundById() { // Arrange - var organizerId = Guid.NewGuid(); + var organizerId = "organizer"; var organizer = new Organizer { Id = organizerId, @@ -124,7 +125,7 @@ public void GetUser_ShouldReturnUserResponseDTO_WhenOrganizerIsFoundById() public void GetUser_ShouldReturnUserResponseDTO_WhenModeratorIsFoundById() { // Arrange - var moderatorId = Guid.NewGuid(); + var moderatorId = "Moderator"; var moderator = new Moderator { Id = moderatorId, @@ -152,7 +153,7 @@ public void GetUser_ShouldReturnUserResponseDTO_WhenModeratorIsFoundById() public void GetUser_ShouldThrowException_WhenNoUserIsAssociatedWithProvidedId() { // Arrange - var userId = Guid.NewGuid(); + var userId = "nobody"; // Setup both organizer and moderator repositories to return null, simulating that no user is found with the provided ID _organizerRepositoryMock.Setup(repo => repo.Get(userId)).Returns(null as Organizer); @@ -172,13 +173,14 @@ public void GetUser_ShouldThrowException_WhenNoUserIsAssociatedWithProvidedId() public void UpdateUser_ShouldReturnTrue_WhenOrganizerIsUpdatedSuccessfully() { // Arrange - var organizerId = Guid.NewGuid(); + var organizerId = "jane-doe"; var actAreaIdModified = Guid.NewGuid(); var updateDto = new UserUpdateDTO { Email = "jane.doe@example.com", Organization = "NewOrg", - ActivityAreaId = actAreaIdModified + ActivityAreaId = actAreaIdModified, + Id = organizerId }; var activity = new ActivityArea @@ -214,13 +216,14 @@ public void UpdateUser_ShouldReturnTrue_WhenOrganizerIsUpdatedSuccessfully() public void UpdateUser_ShouldThrow_WhenActivityAreaIsNotFoundInTheList() { // Arrange - var organizerId = Guid.NewGuid(); + var organizerId = "org"; var badActAreaIdModified = Guid.NewGuid(); var updateDto = new UserUpdateDTO { Email = "jane.doe@example.com", Organization = "NewOrg", - ActivityAreaId = badActAreaIdModified + ActivityAreaId = badActAreaIdModified, + Id = organizerId }; var existingOrganizer = new Organizer @@ -249,10 +252,11 @@ public void UpdateUser_ShouldThrow_WhenActivityAreaIsNotFoundInTheList() public void UpdateUser_ShouldReturnTrue_WhenModeratorIsUpdatedSuccessfully() { // Arrange - var moderatorId = Guid.NewGuid(); + var moderatorId = "mod"; var updateDto = new UserUpdateDTO { - Email = "john.updated@example.com" + Email = "john.updated@example.com", + Id = moderatorId }; var existingModerator = new Moderator From eef6a965847f240a5cb0bef6efe8cddd6395aff6 Mon Sep 17 00:00:00 2001 From: superjekk Date: Fri, 19 Dec 2025 15:02:25 -0500 Subject: [PATCH 12/18] FCT : Migration des types d'Utilisateurs --- core/Data/EventManagementContext.cs | 2 - ...19194542_UserIdTypeReplacement.Designer.cs | 576 ++++++++++++++++++ .../20251219194542_UserIdTypeReplacement.cs | 105 ++++ .../EventManagementContextModelSnapshot.cs | 24 +- 4 files changed, 693 insertions(+), 14 deletions(-) create mode 100644 core/Migrations/20251219194542_UserIdTypeReplacement.Designer.cs create mode 100644 core/Migrations/20251219194542_UserIdTypeReplacement.cs diff --git a/core/Data/EventManagementContext.cs b/core/Data/EventManagementContext.cs index 2993645..546f4fb 100644 --- a/core/Data/EventManagementContext.cs +++ b/core/Data/EventManagementContext.cs @@ -45,14 +45,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(entity => { - entity.Property(e => e.Id).HasDefaultValueSql("gen_random_uuid()"); entity.Property(e => e.CreatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); entity.Property(e => e.UpdatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); }); modelBuilder.Entity(entity => { - entity.Property(e => e.Id).HasDefaultValueSql("gen_random_uuid()"); entity.Property(e => e.CreatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); entity.Property(e => e.UpdatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); }); diff --git a/core/Migrations/20251219194542_UserIdTypeReplacement.Designer.cs b/core/Migrations/20251219194542_UserIdTypeReplacement.Designer.cs new file mode 100644 index 0000000..496dadf --- /dev/null +++ b/core/Migrations/20251219194542_UserIdTypeReplacement.Designer.cs @@ -0,0 +1,576 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using api.core.data; + +#nullable disable + +namespace api.core.Migrations +{ + [DbContext(typeof(EventManagementContext))] + [Migration("20251219194542_UserIdTypeReplacement")] + partial class UserIdTypeReplacement + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("PublicationTag", b => + { + b.Property("PublicationsId") + .HasColumnType("uuid"); + + b.Property("TagsId") + .HasColumnType("uuid"); + + b.HasKey("PublicationsId", "TagsId"); + + b.HasIndex(new[] { "TagsId" }, "IX_PublicationTag_TagsId"); + + b.ToTable("PublicationTag", (string)null); + }); + + modelBuilder.Entity("TagTag", b => + { + b.Property("ChildrenTagsId") + .HasColumnType("uuid"); + + b.Property("ParentTagsId") + .HasColumnType("uuid"); + + b.HasKey("ChildrenTagsId", "ParentTagsId"); + + b.ToTable("TagTag"); + }); + + modelBuilder.Entity("TagsHierarchy", b => + { + b.Property("ChildrenTagsId") + .HasColumnType("uuid"); + + b.Property("ParentTagsId") + .HasColumnType("uuid"); + + b.HasKey("ChildrenTagsId", "ParentTagsId"); + + b.HasIndex(new[] { "ParentTagsId" }, "IX_TagsHierarchy_ParentTagsId"); + + b.ToTable("TagsHierarchy", (string)null); + }); + + modelBuilder.Entity("api.core.data.entities.ActivityArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("NameEn") + .IsRequired() + .HasColumnType("text"); + + b.Property("NameFr") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("ActivityArea"); + }); + + modelBuilder.Entity("api.core.data.entities.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("EventEndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EventStartDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Event"); + }); + + modelBuilder.Entity("api.core.data.entities.Moderator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ActivityAreaId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + + b.HasKey("Id"); + + b.HasIndex("ActivityAreaId"); + + b.ToTable("Moderator"); + }); + + modelBuilder.Entity("api.core.data.entities.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsSent") + .HasColumnType("boolean"); + + b.Property("PublicationId") + .HasColumnType("uuid"); + + b.Property("SubscriptionId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("PublicationId"); + + b.HasIndex("SubscriptionId"); + + b.ToTable("Notification"); + }); + + modelBuilder.Entity("api.core.data.entities.Organizer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ActivityAreaId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscordLink") + .HasColumnType("text"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("FacebookLink") + .HasColumnType("text"); + + b.Property("HasLoggedIn") + .HasColumnType("boolean"); + + b.Property("InstagramLink") + .HasColumnType("text"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LinkedInLink") + .HasColumnType("text"); + + b.Property("Organization") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfileDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("RedditLink") + .HasColumnType("text"); + + b.Property("TikTokLink") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + + b.Property("WebSiteLink") + .HasColumnType("text"); + + b.Property("XLink") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ActivityAreaId"); + + b.ToTable("Organizer"); + }); + + modelBuilder.Entity("api.core.data.entities.Publication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Content") + .HasColumnType("text"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HasBeenReported") + .HasColumnType("boolean"); + + b.Property("ImageAltText") + .HasColumnType("text"); + + b.Property("ImageUrl") + .HasColumnType("text"); + + b.Property("ModeratorId") + .HasColumnType("text"); + + b.Property("OrganizerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("PublicationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Reason") + .HasColumnType("text"); + + b.Property("ReportCount") + .HasColumnType("integer"); + + b.Property("State") + .HasColumnType("integer"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "ModeratorId" }, "IX_Publication_ModeratorId"); + + b.HasIndex(new[] { "OrganizerId" }, "IX_Publication_OrganizerId"); + + b.ToTable("Publication"); + }); + + modelBuilder.Entity("api.core.data.entities.Report", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Category") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PublicationId") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "PublicationId" }, "IX_Report_PublicationId"); + + b.ToTable("Report"); + }); + + modelBuilder.Entity("api.core.data.entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubscriptionToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizerId"); + + b.HasIndex("Email", "OrganizerId") + .IsUnique(); + + b.ToTable("Subscription"); + }); + + modelBuilder.Entity("api.core.data.entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PriorityValue") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + + b.HasKey("Id"); + + b.ToTable("Tag"); + }); + + modelBuilder.Entity("PublicationTag", b => + { + b.HasOne("api.core.data.entities.Publication", null) + .WithMany() + .HasForeignKey("PublicationsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("api.core.data.entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("TagsHierarchy", b => + { + b.HasOne("api.core.data.entities.Tag", null) + .WithMany() + .HasForeignKey("ChildrenTagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("api.core.data.entities.Tag", null) + .WithMany() + .HasForeignKey("ParentTagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("api.core.data.entities.Event", b => + { + b.HasOne("api.core.data.entities.Publication", "Publication") + .WithOne("Event") + .HasForeignKey("api.core.data.entities.Event", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Publication"); + }); + + modelBuilder.Entity("api.core.data.entities.Moderator", b => + { + b.HasOne("api.core.data.entities.ActivityArea", "ActivityArea") + .WithMany("Moderators") + .HasForeignKey("ActivityAreaId"); + + b.Navigation("ActivityArea"); + }); + + modelBuilder.Entity("api.core.data.entities.Notification", b => + { + b.HasOne("api.core.data.entities.Publication", "Publication") + .WithMany("Notifications") + .HasForeignKey("PublicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("api.core.data.entities.Subscription", "Subscription") + .WithMany("Notifications") + .HasForeignKey("SubscriptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Publication"); + + b.Navigation("Subscription"); + }); + + modelBuilder.Entity("api.core.data.entities.Organizer", b => + { + b.HasOne("api.core.data.entities.ActivityArea", "ActivityArea") + .WithMany("Organizers") + .HasForeignKey("ActivityAreaId"); + + b.Navigation("ActivityArea"); + }); + + modelBuilder.Entity("api.core.data.entities.Publication", b => + { + b.HasOne("api.core.data.entities.Moderator", "Moderator") + .WithMany("Publications") + .HasForeignKey("ModeratorId") + .HasConstraintName("Publication_ModeratorId_fkey"); + + b.HasOne("api.core.data.entities.Organizer", "Organizer") + .WithMany("Publications") + .HasForeignKey("OrganizerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("Publication_OrganizerId_fkey"); + + b.Navigation("Moderator"); + + b.Navigation("Organizer"); + }); + + modelBuilder.Entity("api.core.data.entities.Report", b => + { + b.HasOne("api.core.data.entities.Publication", "Publication") + .WithMany("Reports") + .HasForeignKey("PublicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("Report_PublicationId_fkey"); + + b.Navigation("Publication"); + }); + + modelBuilder.Entity("api.core.data.entities.Subscription", b => + { + b.HasOne("api.core.data.entities.Organizer", "Organizer") + .WithMany("Subscriptions") + .HasForeignKey("OrganizerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organizer"); + }); + + modelBuilder.Entity("api.core.data.entities.ActivityArea", b => + { + b.Navigation("Moderators"); + + b.Navigation("Organizers"); + }); + + modelBuilder.Entity("api.core.data.entities.Moderator", b => + { + b.Navigation("Publications"); + }); + + modelBuilder.Entity("api.core.data.entities.Organizer", b => + { + b.Navigation("Publications"); + + b.Navigation("Subscriptions"); + }); + + modelBuilder.Entity("api.core.data.entities.Publication", b => + { + b.Navigation("Event"); + + b.Navigation("Notifications"); + + b.Navigation("Reports"); + }); + + modelBuilder.Entity("api.core.data.entities.Subscription", b => + { + b.Navigation("Notifications"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/core/Migrations/20251219194542_UserIdTypeReplacement.cs b/core/Migrations/20251219194542_UserIdTypeReplacement.cs new file mode 100644 index 0000000..83b4077 --- /dev/null +++ b/core/Migrations/20251219194542_UserIdTypeReplacement.cs @@ -0,0 +1,105 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace api.core.Migrations +{ + /// + public partial class UserIdTypeReplacement : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "OrganizerId", + table: "Subscription", + type: "text", + nullable: false, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "OrganizerId", + table: "Publication", + type: "text", + nullable: false, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "ModeratorId", + table: "Publication", + type: "text", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Organizer", + type: "text", + nullable: false, + oldClrType: typeof(Guid), + oldType: "uuid", + oldDefaultValueSql: "gen_random_uuid()"); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Moderator", + type: "text", + nullable: false, + oldClrType: typeof(Guid), + oldType: "uuid", + oldDefaultValueSql: "gen_random_uuid()"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "OrganizerId", + table: "Subscription", + type: "uuid", + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn( + name: "OrganizerId", + table: "Publication", + type: "uuid", + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn( + name: "ModeratorId", + table: "Publication", + type: "uuid", + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Organizer", + type: "uuid", + nullable: false, + defaultValueSql: "gen_random_uuid()", + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Moderator", + type: "uuid", + nullable: false, + defaultValueSql: "gen_random_uuid()", + oldClrType: typeof(string), + oldType: "text"); + } + } +} diff --git a/core/Migrations/EventManagementContextModelSnapshot.cs b/core/Migrations/EventManagementContextModelSnapshot.cs index 6d38530..68c5e14 100644 --- a/core/Migrations/EventManagementContextModelSnapshot.cs +++ b/core/Migrations/EventManagementContextModelSnapshot.cs @@ -111,10 +111,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("api.core.data.entities.Moderator", b => { - b.Property("Id") + b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasDefaultValueSql("gen_random_uuid()"); + .HasColumnType("text"); b.Property("ActivityAreaId") .HasColumnType("uuid"); @@ -178,10 +177,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("api.core.data.entities.Organizer", b => { - b.Property("Id") + b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasDefaultValueSql("gen_random_uuid()"); + .HasColumnType("text"); b.Property("ActivityAreaId") .HasColumnType("uuid"); @@ -275,11 +273,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ImageUrl") .HasColumnType("text"); - b.Property("ModeratorId") - .HasColumnType("uuid"); + b.Property("ModeratorId") + .HasColumnType("text"); - b.Property("OrganizerId") - .HasColumnType("uuid"); + b.Property("OrganizerId") + .IsRequired() + .HasColumnType("text"); b.Property("PublicationDate") .HasColumnType("timestamp with time zone"); @@ -363,8 +362,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); - b.Property("OrganizerId") - .HasColumnType("uuid"); + b.Property("OrganizerId") + .IsRequired() + .HasColumnType("text"); b.Property("SubscriptionToken") .IsRequired() From 4870275d6779bade1f65447f47349a53430d5359 Mon Sep 17 00:00:00 2001 From: superjekk Date: Thu, 25 Dec 2025 22:59:01 -0500 Subject: [PATCH 13/18] =?UTF-8?q?PROGRES=20:=20R=C3=A9forme=20des=20Utilis?= =?UTF-8?q?ateurs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/Data/Entities/ActivityArea.cs | 8 +++--- core/Data/Entities/Publication.cs | 5 ++-- core/Data/Entities/Subscription.cs | 4 +-- core/Data/Entities/User.cs | 43 +++++++++++++++++++++++++++++ core/Data/EventManagementContext.cs | 19 +++++++------ 5 files changed, 63 insertions(+), 16 deletions(-) diff --git a/core/Data/Entities/ActivityArea.cs b/core/Data/Entities/ActivityArea.cs index 8bd6245..a268ba8 100644 --- a/core/Data/Entities/ActivityArea.cs +++ b/core/Data/Entities/ActivityArea.cs @@ -12,9 +12,9 @@ public partial class ActivityArea : BaseEntity public string NameEn { get; set; } = null!; - [InverseProperty("ActivityArea")] - public virtual ICollection Organizers { get; set; } = new List(); + //[InverseProperty("ActivityArea")] + public virtual ICollection Organizers { get; set; } = new List(); - [InverseProperty("ActivityArea")] - public virtual ICollection Moderators { get; set; } = new List(); + //[InverseProperty("ActivityArea")] + public virtual ICollection Moderators { get; set; } = new List(); } diff --git a/core/Data/Entities/Publication.cs b/core/Data/Entities/Publication.cs index d102221..c23900d 100644 --- a/core/Data/Entities/Publication.cs +++ b/core/Data/Entities/Publication.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using api.core.Data.Entities; using api.core.Data.Enums; using Microsoft.EntityFrameworkCore; @@ -51,11 +52,11 @@ public partial class Publication [ForeignKey("ModeratorId")] [InverseProperty("Publications")] - public virtual Moderator? Moderator { get; set; } + public virtual User? Moderator { get; set; } [ForeignKey("OrganizerId")] [InverseProperty("Publications")] - public virtual Organizer Organizer { get; set; } = null!; + public virtual User Organizer { get; set; } = null!; [InverseProperty("Publication")] public virtual ICollection Reports { get; set; } = new List(); diff --git a/core/Data/Entities/Subscription.cs b/core/Data/Entities/Subscription.cs index 39a6903..cce634e 100644 --- a/core/Data/Entities/Subscription.cs +++ b/core/Data/Entities/Subscription.cs @@ -15,8 +15,8 @@ public partial class Subscription : BaseEntity public string SubscriptionToken { get; set; } = null!; [ForeignKey(nameof(OrganizerId))] - [InverseProperty(nameof(entities.Organizer.Subscriptions))] - public virtual Organizer Organizer { get; set; } = null!; + [InverseProperty(nameof(User.Subscriptions))] + public virtual User Organizer { get; set; } = null!; [InverseProperty(nameof(Notification.Subscription))] public virtual ICollection Notifications { get; set; } = new List(); diff --git a/core/Data/Entities/User.cs b/core/Data/Entities/User.cs index e395769..cd82df0 100644 --- a/core/Data/Entities/User.cs +++ b/core/Data/Entities/User.cs @@ -1,8 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using api.core.data.entities; + namespace api.core.Data.Entities; +[Table(nameof(User))] public class User { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] @@ -18,4 +21,44 @@ public class User public DateTime UpdatedAt { get; set; } public DateTime? DeletedAt { get; set; } + + public UserRole Role { get; set; } + + // Propriétés de Organizer + public string Organization { get; set; } = null!; + + public string ProfileDescription { get; set; } = null!; + + public bool IsActive { get; set; } + + public bool HasLoggedIn { get; set; } + + public string? FacebookLink { get; set; } + + public string? InstagramLink { get; set; } + + public string? TikTokLink { get; set; } + + public string? XLink { get; set; } + + public string? DiscordLink { get; set; } + + public string? LinkedInLink { get; set; } + + public string? RedditLink { get; set; } + + public string? WebSiteLink { get; set; } + + [InverseProperty(nameof(Subscription.Organizer))] + public virtual ICollection Subscriptions { get; set; } = new List(); + + // Adaptation personalisée + public virtual ICollection Publications { get; set; } = new List(); } + +[Flags] +public enum UserRole +{ + Admin = 0b00000001, + Moderator = 0b00000010 +} \ No newline at end of file diff --git a/core/Data/EventManagementContext.cs b/core/Data/EventManagementContext.cs index 546f4fb..457bd6a 100644 --- a/core/Data/EventManagementContext.cs +++ b/core/Data/EventManagementContext.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using api.core.data.entities; +using api.core.Data.Entities; using Microsoft.EntityFrameworkCore; @@ -20,9 +21,11 @@ public EventManagementContext(DbContextOptions options) public virtual DbSet Events { get; set; } - public virtual DbSet Moderators { get; set; } + //public virtual DbSet Moderators { get; set; } - public virtual DbSet Organizers { get; set; } + //public virtual DbSet Organizers { get; set; } + + public virtual DbSet Users { get; set; } public virtual DbSet Publications { get; set; } @@ -43,13 +46,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.Id).ValueGeneratedNever(); }); - modelBuilder.Entity(entity => - { - entity.Property(e => e.CreatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); - entity.Property(e => e.UpdatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); - }); + //modelBuilder.Entity(entity => + //{ + // entity.Property(e => e.CreatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + // entity.Property(e => e.UpdatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + //}); - modelBuilder.Entity(entity => + modelBuilder.Entity(entity => { entity.Property(e => e.CreatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); entity.Property(e => e.UpdatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); From 2e8e4b8ef3396accffc92aad1eeac4c712bbe11c Mon Sep 17 00:00:00 2001 From: superjekk Date: Fri, 26 Dec 2025 17:44:21 -0500 Subject: [PATCH 14/18] =?UTF-8?q?PROGRES=20REFACTOR=20:=20Remplacement=20E?= =?UTF-8?q?ntit=C3=A9s=20sp=C3=A9cialis=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/Data/Entities/Moderator.cs | 16 ----- core/Data/Entities/Organizer.cs | 43 -------------- core/Data/Entities/User.cs | 3 +- core/Data/Responses/UserResponseDTO.cs | 24 ++++++++ .../Abstractions/IModeratorRepository.cs | 8 --- .../Abstractions/IOrganizerRepository.cs | 9 --- .../Abstractions/IUserRepository.cs | 14 +++-- core/Repositories/OrganizerRepository.cs | 59 ------------------- ...deratorRepository.cs => UserRepository.cs} | 40 ++++++++++--- core/Services/EventService.cs | 12 ++-- 10 files changed, 72 insertions(+), 156 deletions(-) delete mode 100644 core/Data/Entities/Moderator.cs delete mode 100644 core/Data/Entities/Organizer.cs delete mode 100644 core/Repositories/Abstractions/IModeratorRepository.cs delete mode 100644 core/Repositories/Abstractions/IOrganizerRepository.cs delete mode 100644 core/Repositories/OrganizerRepository.cs rename core/Repositories/{ModeratorRepository.cs => UserRepository.cs} (50%) diff --git a/core/Data/Entities/Moderator.cs b/core/Data/Entities/Moderator.cs deleted file mode 100644 index 9854801..0000000 --- a/core/Data/Entities/Moderator.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; - -using api.core.Data.Entities; - -namespace api.core.data.entities; - -[Table("Moderator")] -public partial class Moderator : User -{ - [InverseProperty("Moderator")] - public virtual ICollection Publications { get; set; } = new List(); - - [ForeignKey("ActivityAreaId")] - [InverseProperty("Moderators")] - public virtual ActivityArea? ActivityArea { get; set; } -} diff --git a/core/Data/Entities/Organizer.cs b/core/Data/Entities/Organizer.cs deleted file mode 100644 index c4ac9b8..0000000 --- a/core/Data/Entities/Organizer.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; - -using api.core.Data.Entities; - -namespace api.core.data.entities; - -[Table("Organizer")] -public partial class Organizer : User -{ - public string Organization { get; set; } = null!; - - public string ProfileDescription { get; set; } = null!; - - public bool IsActive { get; set; } - - public bool HasLoggedIn { get; set; } - - public string? FacebookLink { get; set; } - - public string? InstagramLink { get; set; } - - public string? TikTokLink { get; set; } - - public string? XLink { get; set; } - - public string? DiscordLink { get; set; } - - public string? LinkedInLink { get; set; } - - public string? RedditLink { get; set; } - - public string? WebSiteLink { get; set; } - - [InverseProperty("Organizer")] - public virtual ICollection Publications { get; set; } = new List(); - - [ForeignKey("ActivityAreaId")] - [InverseProperty("Organizers")] - public virtual ActivityArea? ActivityArea { get; set; } - - [InverseProperty(nameof(Subscription.Organizer))] - public virtual ICollection Subscriptions { get; set; } = new List(); -} diff --git a/core/Data/Entities/User.cs b/core/Data/Entities/User.cs index cd82df0..49fcc8b 100644 --- a/core/Data/Entities/User.cs +++ b/core/Data/Entities/User.cs @@ -60,5 +60,6 @@ public class User public enum UserRole { Admin = 0b00000001, - Moderator = 0b00000010 + Moderator = 0b00000010, + Organizer = 0b00000100 } \ No newline at end of file diff --git a/core/Data/Responses/UserResponseDTO.cs b/core/Data/Responses/UserResponseDTO.cs index 3cc58ea..bff29d4 100644 --- a/core/Data/Responses/UserResponseDTO.cs +++ b/core/Data/Responses/UserResponseDTO.cs @@ -1,4 +1,5 @@ using api.core.data.entities; +using api.core.Data.Entities; namespace api.core.Data.Responses; @@ -86,4 +87,27 @@ public static UserResponseDTO Map(Moderator moderator) UpdatedAt = moderator.UpdatedAt }; } + + public static UserResponseDTO Map(User user) + { + List roles = new List(); + foreach (UserRole role in Enum.GetValues(typeof(UserRole))) + { + if (user.Role.HasFlag(role)) + { + roles.Add(role); + } + } + + return new UserResponseDTO + { + Id = user.Id, + Email = user.Email, + Type = string.Join(',', roles.Select(r => r.ToString())), + IsActive = true, + HasLoggedIn = true, + CreatedAt = user.CreatedAt, + UpdatedAt = user.UpdatedAt + }; + } } diff --git a/core/Repositories/Abstractions/IModeratorRepository.cs b/core/Repositories/Abstractions/IModeratorRepository.cs deleted file mode 100644 index bac4dea..0000000 --- a/core/Repositories/Abstractions/IModeratorRepository.cs +++ /dev/null @@ -1,8 +0,0 @@ -using api.core.data.entities; -using api.core.Repositories.Abstractions; - -namespace api.core.repositories.abstractions; - -public interface IModeratorRepository : IUserRepository -{ -} diff --git a/core/Repositories/Abstractions/IOrganizerRepository.cs b/core/Repositories/Abstractions/IOrganizerRepository.cs deleted file mode 100644 index 2872c81..0000000 --- a/core/Repositories/Abstractions/IOrganizerRepository.cs +++ /dev/null @@ -1,9 +0,0 @@ -using api.core.data.entities; -using api.core.Data.requests; -using api.core.Repositories.Abstractions; - -namespace api.core.repositories.abstractions; - -public interface IOrganizerRepository : IUserRepository -{ -} diff --git a/core/Repositories/Abstractions/IUserRepository.cs b/core/Repositories/Abstractions/IUserRepository.cs index 930e839..546f3c1 100644 --- a/core/Repositories/Abstractions/IUserRepository.cs +++ b/core/Repositories/Abstractions/IUserRepository.cs @@ -2,11 +2,13 @@ namespace api.core.Repositories.Abstractions; -public interface IUserRepository where T : User +public interface IUserRepository { - public T Add(T entity); - public bool Delete(T entity); - public T? Get(string id); - public IQueryable GetAll(); - public bool Update(string id, T entity); + public User Add(User entity); + public bool Delete(User entity); + public User? Get(string id); + public IQueryable GetAll(); + public bool Update(string id, User entity); + public User? GetOrganizer(string id); + public User? GetModerator(string id); } diff --git a/core/Repositories/OrganizerRepository.cs b/core/Repositories/OrganizerRepository.cs deleted file mode 100644 index 9e57297..0000000 --- a/core/Repositories/OrganizerRepository.cs +++ /dev/null @@ -1,59 +0,0 @@ -using api.core.data; -using api.core.data.entities; -using api.core.repositories.abstractions; - -using Microsoft.EntityFrameworkCore; - -namespace api.core.repositories; - -public class OrganizerRepository(EventManagementContext context) : IOrganizerRepository -{ - public Organizer Add(Organizer entity) - { - var inserted = context.Organizers.Add(entity); - - if (inserted.Entity != null) - { - context.SaveChanges(); - return inserted.Entity; - } - throw new Exception($"Unable to create an organizer {entity.Id}"); - } - - public bool Delete(Organizer entity) - { - throw new NotImplementedException(); - } - - public Organizer? Get(string id) - { - var entity = context.Organizers - .Include(x => x.ActivityArea) - .FirstOrDefault(x => x.Id == id); - if (entity != null && entity.DeletedAt == null) - { - return entity; - } - return null; - } - - public IQueryable GetAll() - { - return context.Organizers - .Include(x => x.ActivityArea); - } - - public bool Update(string id, Organizer entity) - { - var existingEntity = Get(id); - - if (existingEntity != null) - { - context.Entry(existingEntity).CurrentValues.SetValues(entity); - context.SaveChanges(); - return true; - } - - return false; - } -} diff --git a/core/Repositories/ModeratorRepository.cs b/core/Repositories/UserRepository.cs similarity index 50% rename from core/Repositories/ModeratorRepository.cs rename to core/Repositories/UserRepository.cs index aa79672..2b954ba 100644 --- a/core/Repositories/ModeratorRepository.cs +++ b/core/Repositories/UserRepository.cs @@ -1,14 +1,16 @@ using api.core.data; using api.core.data.entities; +using api.core.Data.Entities; using api.core.repositories.abstractions; +using api.core.Repositories.Abstractions; namespace api.core.repositories; -public class ModeratorRepository(EventManagementContext context) : IModeratorRepository +public class UserRepository(EventManagementContext context) : IUserRepository { - public Moderator Add(Moderator entity) + public User Add(User entity) { - var inserted = context.Moderators.Add(entity); + var inserted = context.Users.Add(entity); if (inserted.Entity != null) { @@ -18,14 +20,14 @@ public Moderator Add(Moderator entity) throw new Exception($"Unable to create a Moderator {entity.Id}"); } - public bool Delete(Moderator entity) + public bool Delete(User entity) { throw new NotImplementedException(); } - public Moderator? Get(string id) + public User? Get(string id) { - var entity = context.Moderators.Find(id); + var entity = context.Users.Find(id); if (entity != null && entity.DeletedAt == null) { return entity; @@ -33,12 +35,12 @@ public bool Delete(Moderator entity) return null; } - public IQueryable GetAll() + public IQueryable GetAll() { throw new NotImplementedException(); } - public bool Update(string id, Moderator entity) + public bool Update(string id, User entity) { var existingEntity = Get(id); @@ -51,4 +53,26 @@ public bool Update(string id, Moderator entity) return false; } + + public User? GetOrganizer(string id) + { + var user = Get(id); + if (user == null || !user.Role.HasFlag(UserRole.Organizer)) + { + return null; + } + + return user; + } + + public User? GetModerator(string id) + { + var user = Get(id); + if (user == null || !user.Role.HasFlag(UserRole.Moderator)) + { + return null; + } + + return user; + } } diff --git a/core/Services/EventService.cs b/core/Services/EventService.cs index 73eda5b..4a2876d 100644 --- a/core/Services/EventService.cs +++ b/core/Services/EventService.cs @@ -13,6 +13,7 @@ using api.core.Extensions; using Microsoft.IdentityModel.Tokens; +using api.core.Repositories.Abstractions; namespace api.core.Services; @@ -21,8 +22,7 @@ public class EventService( IConfiguration config, IEventRepository evntRepo, ITagService tagService, - IOrganizerRepository orgRepo, - IModeratorRepository moderatorRepo, + IUserRepository userRepository, IFileShareService fileShareService, IEmailService emailService, IImageService imageService, @@ -69,7 +69,7 @@ public EventResponseDTO GetEvent(Guid id) public EventResponseDTO AddEvent(string userId, EventCreationRequestDTO request) { - var organizer = orgRepo.Get(userId) ?? throw new UnauthorizedException(); + var organizer = userRepository.GetOrganizer(userId) ?? throw new UnauthorizedException(); if (request.Tags.Count > 5) throw new BadParameterException(nameof(request.Tags), "Too many tags"); @@ -112,7 +112,7 @@ public EventResponseDTO AddEvent(string userId, EventCreationRequestDTO request) public EventResponseDTO AddDraftEvent(string userId, DraftEventRequestDTO request) { - var organizer = orgRepo.Get(userId) ?? throw new UnauthorizedException(); + var organizer = userRepository.GetOrganizer(userId) ?? throw new UnauthorizedException(); if (request.Tags.Count > 5) throw new BadParameterException(nameof(request.Tags), "Too many tags"); @@ -167,7 +167,7 @@ public bool DeleteEvent(string userId, Guid eventId) public bool UpdateEvent(string userId, Guid eventId, EventUpdateRequestDTO request) { - _ = orgRepo.Get(userId) + _ = userRepository.GetOrganizer(userId) ?? throw new UnauthorizedException(); if (request.Tags.Count > 5) @@ -206,7 +206,7 @@ public bool UpdateEvent(string userId, Guid eventId, EventUpdateRequestDTO reque public bool UpdateEventState(string userId, Guid eventId, State state, string? reason) { - var moderator = moderatorRepo.Get(userId) ?? throw new UnauthorizedException(); + var moderator = userRepository.GetModerator(userId) ?? throw new UnauthorizedException(); var evnt = evntRepo.Get(eventId); if (evnt!.Publication.ModeratorId == null) From c919d4f28c557fd0dc4664747095b36b69bd2e8a Mon Sep 17 00:00:00 2001 From: superjekk Date: Sat, 27 Dec 2025 00:06:20 -0500 Subject: [PATCH 15/18] COMPILATION FIX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les références obsolètes ont été modifiées afin de permettre que la solution compile avec le nouveau contexte --- core/Controllers/OrganizersController.cs | 3 +- core/Data/Entities/User.cs | 3 + core/Data/Responses/UserResponseDTO.cs | 96 +++++++++++-------- .../DependencyInjectionExtension.cs | 4 +- core/Services/DraftEventService.cs | 5 +- core/Services/ModeratorService.cs | 9 +- core/Services/UserService.cs | 23 +++-- tests/Tests/Services/EventServiceTests.cs | 83 ++++++++-------- .../Services/NotificationServiceTests.cs | 7 +- tests/Tests/Services/UserServiceTests.cs | 76 ++++++++------- 10 files changed, 169 insertions(+), 140 deletions(-) diff --git a/core/Controllers/OrganizersController.cs b/core/Controllers/OrganizersController.cs index 2f8837d..5b27241 100644 --- a/core/Controllers/OrganizersController.cs +++ b/core/Controllers/OrganizersController.cs @@ -1,5 +1,6 @@ using api.core.data.entities; using api.core.Data; +using api.core.Data.Entities; using api.core.Data.Enums; using api.core.Data.Exceptions; using api.core.Data.requests; @@ -55,7 +56,7 @@ public IActionResult GetOrganizer(string organizerId) { Data = user }) - : throw new NotFoundException(); + : throw new NotFoundException(); } /// diff --git a/core/Data/Entities/User.cs b/core/Data/Entities/User.cs index 49fcc8b..afd085a 100644 --- a/core/Data/Entities/User.cs +++ b/core/Data/Entities/User.cs @@ -49,6 +49,9 @@ public class User public string? WebSiteLink { get; set; } + [ForeignKey("ActivityAreaId")] + public virtual ActivityArea? ActivityArea { get; set; } + [InverseProperty(nameof(Subscription.Organizer))] public virtual ICollection Subscriptions { get; set; } = new List(); diff --git a/core/Data/Responses/UserResponseDTO.cs b/core/Data/Responses/UserResponseDTO.cs index bff29d4..254a49a 100644 --- a/core/Data/Responses/UserResponseDTO.cs +++ b/core/Data/Responses/UserResponseDTO.cs @@ -47,46 +47,46 @@ public class UserResponseDTO public DateTime UpdatedAt { get; set; } - public static UserResponseDTO Map(Organizer organizer) - { - return new UserResponseDTO - { - Id = organizer.Id, - Email = organizer.Email, - Type = "Organizer", - Organization = organizer.Organization, - ActivityArea = organizer.ActivityArea != null ? - ActivityAreaResponseDTO.Map(organizer.ActivityArea) : - null, - IsActive = organizer.IsActive, - HasLoggedIn = organizer.HasLoggedIn, - ProfileDescription = organizer.ProfileDescription, - FacebookLink = organizer.FacebookLink, - InstagramLink = organizer.InstagramLink, - TikTokLink = organizer.TikTokLink, - XLink = organizer.XLink, - DiscordLink = organizer.DiscordLink, - LinkedInLink = organizer.LinkedInLink, - RedditLink = organizer.RedditLink, - WebSiteLink = organizer.WebSiteLink, - CreatedAt = organizer.CreatedAt, - UpdatedAt = organizer.UpdatedAt - }; - } - - public static UserResponseDTO Map(Moderator moderator) - { - return new UserResponseDTO - { - Id = moderator.Id, - Email = moderator.Email, - Type = "Moderator", - IsActive = true, - HasLoggedIn = true, - CreatedAt = moderator.CreatedAt, - UpdatedAt = moderator.UpdatedAt - }; - } + //public static UserResponseDTO Map(Organizer organizer) + //{ + // return new UserResponseDTO + // { + // Id = organizer.Id, + // Email = organizer.Email, + // Type = "Organizer", + // Organization = organizer.Organization, + // ActivityArea = organizer.ActivityArea != null ? + // ActivityAreaResponseDTO.Map(organizer.ActivityArea) : + // null, + // IsActive = organizer.IsActive, + // HasLoggedIn = organizer.HasLoggedIn, + // ProfileDescription = organizer.ProfileDescription, + // FacebookLink = organizer.FacebookLink, + // InstagramLink = organizer.InstagramLink, + // TikTokLink = organizer.TikTokLink, + // XLink = organizer.XLink, + // DiscordLink = organizer.DiscordLink, + // LinkedInLink = organizer.LinkedInLink, + // RedditLink = organizer.RedditLink, + // WebSiteLink = organizer.WebSiteLink, + // CreatedAt = organizer.CreatedAt, + // UpdatedAt = organizer.UpdatedAt + // }; + //} + + //public static UserResponseDTO Map(Moderator moderator) + //{ + // return new UserResponseDTO + // { + // Id = moderator.Id, + // Email = moderator.Email, + // Type = "Moderator", + // IsActive = true, + // HasLoggedIn = true, + // CreatedAt = moderator.CreatedAt, + // UpdatedAt = moderator.UpdatedAt + // }; + //} public static UserResponseDTO Map(User user) { @@ -104,8 +104,20 @@ public static UserResponseDTO Map(User user) Id = user.Id, Email = user.Email, Type = string.Join(',', roles.Select(r => r.ToString())), - IsActive = true, - HasLoggedIn = true, + IsActive = user.IsActive, + HasLoggedIn = user.HasLoggedIn, + ProfileDescription = user.ProfileDescription, + FacebookLink = user.FacebookLink, + InstagramLink = user.InstagramLink, + TikTokLink = user.TikTokLink, + XLink = user.XLink, + DiscordLink = user.DiscordLink, + LinkedInLink = user.LinkedInLink, + RedditLink = user.RedditLink, + WebSiteLink = user.WebSiteLink, + Organization = user.Organization, + ActivityArea = user.ActivityArea != null ? + ActivityAreaResponseDTO.Map(user.ActivityArea) : null, CreatedAt = user.CreatedAt, UpdatedAt = user.UpdatedAt }; diff --git a/core/Extensions/DependencyInjectionExtension.cs b/core/Extensions/DependencyInjectionExtension.cs index 92f5d82..7756238 100644 --- a/core/Extensions/DependencyInjectionExtension.cs +++ b/core/Extensions/DependencyInjectionExtension.cs @@ -1,6 +1,7 @@ using api.core.Misc; using api.core.repositories; using api.core.repositories.abstractions; +using api.core.Repositories.Abstractions; using api.core.services.abstractions; using api.core.Services; using api.core.Services.Abstractions; @@ -19,10 +20,9 @@ public static IServiceCollection AddDependencyInjection(this IServiceCollection services.AddTransient(); // Repositories - services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/core/Services/DraftEventService.cs b/core/Services/DraftEventService.cs index 87d1294..a923ca4 100644 --- a/core/Services/DraftEventService.cs +++ b/core/Services/DraftEventService.cs @@ -17,19 +17,20 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using api.core.Services.Abstractions; +using api.core.Repositories.Abstractions; namespace api.core.Services; public class DraftEventService( IEventRepository evntRepo, ITagService tagService, - IOrganizerRepository orgRepo, + IUserRepository orgRepo, IFileShareService fileShareService, IImageService imageService) : IDraftEventService { public EventResponseDTO AddDraftEvent(string userId, DraftEventRequestDTO request) { - var organizer = orgRepo.Get(userId) ?? throw new UnauthorizedException(); + var organizer = orgRepo.GetOrganizer(userId) ?? throw new UnauthorizedException(); if (request.Tags.Count > 5) throw new BadParameterException(nameof(request.Tags), "Too many tags"); diff --git a/core/Services/ModeratorService.cs b/core/Services/ModeratorService.cs index d72278f..6ed80f3 100644 --- a/core/Services/ModeratorService.cs +++ b/core/Services/ModeratorService.cs @@ -1,12 +1,14 @@ using api.core.data.entities; +using api.core.Data.Entities; using api.core.Data.Exceptions; using api.core.Data.Requests; using api.core.repositories.abstractions; +using api.core.Repositories.Abstractions; using api.core.services.abstractions; namespace api.core.Services; -public class ModeratorService(IModeratorRepository moderatorRepository, IConfiguration configuration) : IModeratorService +public class ModeratorService(IUserRepository userRepository, IConfiguration configuration) : IModeratorService { public ModeratorResponseDTO CreateModerator(string apiKey, ModeratorCreateRequestDTO req) { @@ -18,12 +20,13 @@ public ModeratorResponseDTO CreateModerator(string apiKey, ModeratorCreateReques throw new UnauthorizedException(); // Create the moderator - moderatorRepository.Add(new Moderator + userRepository.Add(new User { Id = req.Id, Email = req.Email, CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + UpdatedAt = DateTime.UtcNow, + Role = UserRole.Moderator }); return new ModeratorResponseDTO diff --git a/core/Services/UserService.cs b/core/Services/UserService.cs index 6638b49..66c7cbe 100644 --- a/core/Services/UserService.cs +++ b/core/Services/UserService.cs @@ -1,4 +1,4 @@ -using api.core.data.entities; +using api.core.data.entities; using api.core.Data.Exceptions; using api.core.Data.Enums; using api.core.Data.requests; @@ -9,13 +9,15 @@ using SixLabors.ImageSharp; +using api.core.Misc; +using api.core.Repositories.Abstractions; +using api.core.Data.Entities; namespace api.core.Services; public class UserService( - IOrganizerRepository organizerRepository, + IUserRepository userRepository, IFileShareService fileShareService, - IModeratorRepository moderatorRepository, ITagRepository tagRepository, IActivityAreaRepository activityAreaRepository, IImageService imageService) : IUserService @@ -30,7 +32,7 @@ public UserResponseDTO AddOrganizer(string id, UserCreateDTO organizerDto) NotFoundException.ThrowIfNull(activityArea); } - var inserted = organizerRepository.Add(new Organizer + var inserted = userRepository.Add(new User { Id = id, Email = organizerDto.Email, @@ -40,7 +42,8 @@ public UserResponseDTO AddOrganizer(string id, UserCreateDTO organizerDto) IsActive = true, HasLoggedIn = false, CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + UpdatedAt = DateTime.UtcNow, + Role = UserRole.Organizer }); var avatarUri = fileShareService.FileGetDownloadUri($"{id}/{AVATAR_FILE_NAME}"); @@ -80,7 +83,7 @@ public string GetUserAvatarUrl(string id) public IEnumerable GetUsers(string? search, OrganizerAccountActiveFilter activeFilter, out int count) { - var organizers = organizerRepository.GetAll() + var organizers = userRepository.GetAll() .Where(x => (search == null || search.Equals("") || x.Organization.ToLower().Contains(search!.ToLower() ?? "") || x.Email.ToLower().Contains(search!.ToLower() ?? "")) && @@ -97,9 +100,9 @@ public bool ToggleUserActiveState(string id) { EnsureIsOrganizer(id); - var user = organizerRepository.Get(id); + var user = userRepository.Get(id); user!.IsActive = !user.IsActive; - return organizerRepository.Update(id, user); + return userRepository.Update(id, user); } private void EnsureIsOrganizer(string id) @@ -122,14 +125,14 @@ public bool UpdateUser(string id, UserUpdateDTO dto) return user.Type switch { - "Moderator" => moderatorRepository.Update(id, new Moderator + "Moderator" => userRepository.Update(id, new User { Id = id, Email = dto.Email, CreatedAt = user.CreatedAt, UpdatedAt = DateTime.UtcNow }), - "Organizer" => organizerRepository.Update(id, new Organizer + "Organizer" => userRepository.Update(id, new User { Id = id, Email = dto.Email, diff --git a/tests/Tests/Services/EventServiceTests.cs b/tests/Tests/Services/EventServiceTests.cs index 808e6c5..f20acc5 100644 --- a/tests/Tests/Services/EventServiceTests.cs +++ b/tests/Tests/Services/EventServiceTests.cs @@ -1,8 +1,10 @@ using api.core.data.entities; +using api.core.Data.Entities; using api.core.Data.Enums; using api.core.Data.Exceptions; using api.core.Data.requests; using api.core.repositories.abstractions; +using api.core.Repositories.Abstractions; using api.core.services.abstractions; using api.core.Services; using api.core.Services.Abstractions; @@ -46,20 +48,22 @@ public class EventServiceTests Name = "Test" } ], - Organizer = new Organizer + Organizer = new User { Id = Guid.NewGuid().ToString(), ActivityAreaId = ActivityAreaClubId, - ActivityArea = new ActivityArea - { - Id = ActivityAreaClubId, - NameEn = "Club", - NameFr = "Club" - } + //ActivityArea = new ActivityArea + //{ + // Id = ActivityAreaClubId, + // NameEn = "Club", + // NameFr = "Club" + //}, + Role = UserRole.Organizer }, - Moderator = new Moderator + Moderator = new User { Id = Guid.NewGuid().ToString(), + Role = UserRole.Moderator }, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now @@ -85,16 +89,17 @@ public class EventServiceTests Name = "Test" } ], - Organizer = new Organizer + Organizer = new User { Id = Guid.NewGuid().ToString(), ActivityAreaId = ActivityAreaSchoolId, - ActivityArea = new ActivityArea - { - Id = ActivityAreaSchoolId, - NameEn = "School", - NameFr = "School" - } + //ActivityArea = new ActivityArea + //{ + // Id = ActivityAreaSchoolId, + // NameEn = "School", + // NameFr = "School" + //} + Role = UserRole.Organizer }, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now @@ -113,16 +118,16 @@ public class EventServiceTests State = State.Published, PublicationDate = DateTime.UtcNow, Tags = [], - Organizer = new Organizer + Organizer = new User { Id = Guid.NewGuid().ToString(), ActivityAreaId = ActivityAreaSchoolId, - ActivityArea = new ActivityArea - { - Id = ActivityAreaSchoolId, - NameEn = "School", - NameFr = "School" - } + //ActivityArea = new ActivityArea + //{ + // Id = ActivityAreaSchoolId, + // NameEn = "School", + // NameFr = "School" + //} }, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now @@ -148,16 +153,16 @@ public class EventServiceTests Name = "Test" } ], - Organizer = new Organizer + Organizer = new User { Id = Guid.NewGuid().ToString(), ActivityAreaId = ActivityAreaSchoolId, - ActivityArea = new ActivityArea - { - Id = ActivityAreaSchoolId, - NameEn = "School", - NameFr = "School" - } + //ActivityArea = new ActivityArea + //{ + // Id = ActivityAreaSchoolId, + // NameEn = "School", + // NameFr = "School" + //} }, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now, @@ -169,8 +174,7 @@ public class EventServiceTests private readonly Mock _mockEventRepository; private readonly Mock _mockTagService; - private readonly Mock _mockOrganizerRepository; - private readonly Mock _mockModeratorRepository; + private readonly Mock _mockUserRepository; private readonly Mock _mockFileShareService; private readonly Mock _mockEmailService; private readonly Mock _mockImageService; @@ -181,8 +185,7 @@ public EventServiceTests() { _mockEventRepository = new Mock(); _mockTagService = new Mock(); - _mockOrganizerRepository = new Mock(); - _mockModeratorRepository = new Mock(); + _mockUserRepository = new Mock(); _mockFileShareService = new Mock(); _mockEmailService = new Mock(); _mockImageService = new Mock(); @@ -194,8 +197,7 @@ public EventServiceTests() _mockConfig.Object, _mockEventRepository.Object, _mockTagService.Object, - _mockOrganizerRepository.Object, - _mockModeratorRepository.Object, + _mockUserRepository.Object, _mockFileShareService.Object, _mockEmailService.Object, _mockImageService.Object, @@ -335,7 +337,7 @@ public void GetEvent_ShouldReturnEvent() public void AddEvents_ShouldThrowAnExceptionWhenOrganizerIsUnknown() { // Arrange - _mockOrganizerRepository.Setup(repo => repo.Get(It.IsAny())).Returns((Organizer?)null); + _mockUserRepository.Setup(repo => repo.GetOrganizer(It.IsAny())).Returns((User?)null); // Act _eventService.Invoking(s => @@ -420,14 +422,13 @@ public void UpdateEvent_ShouldReturnTrue_WhenEventIsUpdatedSuccessfully() _mockEventRepository.Setup(repo => repo.Get(eventId)).Returns(_events.First()); _mockEventRepository.Setup(repo => repo.Update(eventId, It.IsAny())).Returns(true); - _mockOrganizerRepository.Setup(repo => repo.Get(It.IsAny())).Returns(new Organizer { Id = userId }); + _mockUserRepository.Setup(repo => repo.GetOrganizer(It.IsAny())).Returns(new User { Id = userId, Role = UserRole.Organizer }); _eventService = new EventService( _mockConfig.Object, _mockEventRepository.Object, _mockTagService.Object, - _mockOrganizerRepository.Object, - _mockModeratorRepository.Object, + _mockUserRepository.Object, _mockFileShareService.Object, _mockEmailService.Object, _mockImageService.Object, @@ -488,7 +489,7 @@ public void UpdateEventState_ShouldReturnTrue_WhenStateIsUpdatedSuccessfullyByMo _mockEventRepository.Setup(repo => repo.Get(eventId)).Returns(eventToUpdate); _mockEventRepository.Setup(repo => repo.Update(eventId, It.IsAny())).Returns(true); - _mockModeratorRepository.Setup(repo => repo.Get(It.IsAny())).Returns(new Moderator { Id = userId }); + _mockUserRepository.Setup(repo => repo.GetModerator(It.IsAny())).Returns(new User { Id = userId, Role = UserRole.Moderator }); _mockEmailService.Setup(service => service.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); // Act @@ -513,7 +514,7 @@ public void UpdateEventState_ShouldHaveStatePublished_WhenStateIsUpdatedWithAppr _mockEventRepository.Setup(repo => repo.Get(eventId)).Returns(eventToUpdate); _mockEventRepository.Setup(repo => repo.Update(eventId, It.IsAny())).Returns(true); - _mockModeratorRepository.Setup(repo => repo.Get(It.IsAny())).Returns(new Moderator { Id = userId }); + _mockUserRepository.Setup(repo => repo.GetModerator(It.IsAny())).Returns(new User { Id = userId, Role = UserRole.Moderator }); _mockEmailService.Setup(service => service.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); // Act diff --git a/tests/Tests/Services/NotificationServiceTests.cs b/tests/Tests/Services/NotificationServiceTests.cs index e6b1c19..be668a1 100644 --- a/tests/Tests/Services/NotificationServiceTests.cs +++ b/tests/Tests/Services/NotificationServiceTests.cs @@ -9,6 +9,7 @@ using api.emails.Models; using Microsoft.Extensions.Configuration; using api.emails; +using api.core.Data.Entities; namespace api.tests.Tests.Services; @@ -248,10 +249,11 @@ public async Task SendNewsForRemainingPublication_ShouldSendNotificationWaiting( { Id = pubId, OrganizerId = orgId, - Organizer = new Organizer + Organizer = new User { Id = orgId, Email = "blabla@bla.com", + Role = UserRole.Organizer } }, IsSent = false, @@ -305,10 +307,11 @@ public async Task SendNewsForRemainingPublication_ShouldReturn0Early() { Id = pubId, OrganizerId = orgId, - Organizer = new Organizer + Organizer = new User { Id = orgId, Email = "blabla@bla.com", + Role = UserRole.Organizer } }, IsSent = true, // Won't be sent twice diff --git a/tests/Tests/Services/UserServiceTests.cs b/tests/Tests/Services/UserServiceTests.cs index d4e5a64..4dd4871 100644 --- a/tests/Tests/Services/UserServiceTests.cs +++ b/tests/Tests/Services/UserServiceTests.cs @@ -11,12 +11,13 @@ using System.Diagnostics; using api.core.Data.Exceptions; using api.core.services.abstractions; +using api.core.Data.Entities; +using api.core.Repositories.Abstractions; namespace api.tests.Tests.Services; public class UserServiceTests { - private readonly Mock _organizerRepositoryMock; - private readonly Mock _moderatorRepositoryMock; + private readonly Mock _userRepositoryMock; private readonly Mock _activityAreaRepositoryMock; private readonly Mock _tagRepositoryMock; private readonly Mock _fileShareServiceMock; @@ -25,8 +26,7 @@ public class UserServiceTests public UserServiceTests() { - _organizerRepositoryMock = new Mock(); - _moderatorRepositoryMock = new Mock(); + _userRepositoryMock = new Mock(); _tagRepositoryMock = new Mock(); _activityAreaRepositoryMock = new Mock(); _fileShareServiceMock = new Mock(); @@ -34,9 +34,8 @@ public UserServiceTests() _fileShareServiceMock.Setup(service => service.FileGetDownloadUri(It.IsAny())).Returns(new Uri("http://example.com/avatar.webp")); _userService = new UserService( - _organizerRepositoryMock.Object, + _userRepositoryMock.Object, _fileShareServiceMock.Object, - _moderatorRepositoryMock.Object, _tagRepositoryMock.Object, _activityAreaRepositoryMock.Object, _imageServiceMock.Object); @@ -60,7 +59,7 @@ public void AddOrganizer_ShouldReturnUserResponseDTO_WhenOrganizerIsAddedSuccess Id = actAreaModified, NameFr = "Tech", }; - var organizer = new Organizer + var organizer = new User { Email = organizerDto.Email, Organization = organizerDto.Organization, @@ -72,7 +71,7 @@ public void AddOrganizer_ShouldReturnUserResponseDTO_WhenOrganizerIsAddedSuccess _activityAreaRepositoryMock.Setup(repo => repo.Get(It.IsAny())).Returns(activity); - _organizerRepositoryMock.Setup(repo => repo.Add(It.IsAny())).Returns(organizer); + _userRepositoryMock.Setup(repo => repo.Add(It.IsAny())).Returns(organizer); // Act var result = _userService.AddOrganizer("1234", organizerDto); @@ -83,7 +82,7 @@ public void AddOrganizer_ShouldReturnUserResponseDTO_WhenOrganizerIsAddedSuccess result.Organization.Should().Be(organizerDto.Organization); result.ActivityArea.Id.ToString().Should().Be(actAreaModified.ToString()); - _organizerRepositoryMock.Verify(repo => repo.Add(It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.Add(It.IsAny()), Times.Once); } [Fact] @@ -91,7 +90,7 @@ public void GetUser_ShouldReturnUserResponseDTO_WhenOrganizerIsFoundById() { // Arrange var organizerId = "organizer"; - var organizer = new Organizer + var organizer = new User { Id = organizerId, Email = "john.doe@example.com", @@ -105,10 +104,11 @@ public void GetUser_ShouldReturnUserResponseDTO_WhenOrganizerIsFoundById() UpdatedAt = DateTime.UtcNow }, CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + UpdatedAt = DateTime.UtcNow, + Role = UserRole.Organizer }; - _organizerRepositoryMock.Setup(repo => repo.Get(organizerId)).Returns(organizer); + _userRepositoryMock.Setup(repo => repo.GetOrganizer(organizerId)).Returns(organizer); // Act var result = _userService.GetUser(organizerId); @@ -118,7 +118,7 @@ public void GetUser_ShouldReturnUserResponseDTO_WhenOrganizerIsFoundById() result.Id.Should().Be(organizerId); result.Email.Should().Be(organizer.Email); - _organizerRepositoryMock.Verify(repo => repo.Get(organizerId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetOrganizer(organizerId), Times.Once); } [Fact] @@ -126,16 +126,17 @@ public void GetUser_ShouldReturnUserResponseDTO_WhenModeratorIsFoundById() { // Arrange var moderatorId = "Moderator"; - var moderator = new Moderator + var moderator = new User { Id = moderatorId, Email = "jane.doe@example.com", CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + UpdatedAt = DateTime.UtcNow, + Role = UserRole.Moderator }; - _organizerRepositoryMock.Setup(repo => repo.Get(moderatorId)).Returns((Organizer?)null); // Simulate no organizer found - _moderatorRepositoryMock.Setup(repo => repo.Get(moderatorId)).Returns(moderator); // Simulate moderator found + _userRepositoryMock.Setup(repo => repo.GetOrganizer(moderatorId)).Returns((User?)null); // Simulate no organizer found + _userRepositoryMock.Setup(repo => repo.GetModerator(moderatorId)).Returns(moderator); // Simulate moderator found // Act var result = _userService.GetUser(moderatorId); @@ -145,8 +146,8 @@ public void GetUser_ShouldReturnUserResponseDTO_WhenModeratorIsFoundById() result.Id.Should().Be(moderatorId); result.Email.Should().Be(moderator.Email); - _organizerRepositoryMock.Verify(repo => repo.Get(moderatorId), Times.Once); - _moderatorRepositoryMock.Verify(repo => repo.Get(moderatorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetOrganizer(moderatorId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetModerator(moderatorId), Times.Once); } [Fact] @@ -156,16 +157,16 @@ public void GetUser_ShouldThrowException_WhenNoUserIsAssociatedWithProvidedId() var userId = "nobody"; // Setup both organizer and moderator repositories to return null, simulating that no user is found with the provided ID - _organizerRepositoryMock.Setup(repo => repo.Get(userId)).Returns(null as Organizer); - _moderatorRepositoryMock.Setup(repo => repo.Get(userId)).Returns(null as Moderator); + _userRepositoryMock.Setup(repo => repo.GetOrganizer(userId)).Returns(null as User); + _userRepositoryMock.Setup(repo => repo.GetModerator(userId)).Returns(null as User); // Act Action act = () => _userService.GetUser(userId); // Assert act.Should().Throw().WithMessage("No users associated with this ID"); - _organizerRepositoryMock.Verify(repo => repo.Get(userId), Times.Once); - _moderatorRepositoryMock.Verify(repo => repo.Get(userId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetOrganizer(userId), Times.Once); + _userRepositoryMock.Verify(repo => repo.GetOrganizer(userId), Times.Once); } @@ -189,19 +190,19 @@ public void UpdateUser_ShouldReturnTrue_WhenOrganizerIsUpdatedSuccessfully() NameFr = "Tech", }; - var existingOrganizer = new Organizer + var existingOrganizer = new User { Id = organizerId, Email = "john.doe@example.com", Organization = "ExampleOrg", - ActivityArea = activity, CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + UpdatedAt = DateTime.UtcNow, + Role = UserRole.Organizer }; _activityAreaRepositoryMock.Setup(repo => repo.Get(actAreaIdModified)).Returns(activity); // Simulate activity area found - _organizerRepositoryMock.Setup(repo => repo.Get(organizerId)).Returns(existingOrganizer); - _organizerRepositoryMock.Setup(repo => repo.Update(organizerId, It.IsAny())).Returns(true); + _userRepositoryMock.Setup(repo => repo.GetOrganizer(organizerId)).Returns(existingOrganizer); + _userRepositoryMock.Setup(repo => repo.Update(organizerId, It.IsAny())).Returns(true); // Act var result = _userService.UpdateUser(organizerId, updateDto); @@ -209,7 +210,7 @@ public void UpdateUser_ShouldReturnTrue_WhenOrganizerIsUpdatedSuccessfully() // Assert result.Should().BeTrue(); - _organizerRepositoryMock.Verify(repo => repo.Update(organizerId, It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.Update(organizerId, It.IsAny()), Times.Once); } [Fact] @@ -226,7 +227,7 @@ public void UpdateUser_ShouldThrow_WhenActivityAreaIsNotFoundInTheList() Id = organizerId }; - var existingOrganizer = new Organizer + var existingOrganizer = new User { Id = organizerId, Email = "john.doe@example.com", @@ -240,8 +241,8 @@ public void UpdateUser_ShouldThrow_WhenActivityAreaIsNotFoundInTheList() UpdatedAt = DateTime.UtcNow }; _activityAreaRepositoryMock.Setup(repo => repo.Get(badActAreaIdModified)).Returns(null as ActivityArea); // Simulate activity area not found - _organizerRepositoryMock.Setup(repo => repo.Get(organizerId)).Returns(existingOrganizer); - _organizerRepositoryMock.Setup(repo => repo.Update(organizerId, It.IsAny())).Returns(true); + _userRepositoryMock.Setup(repo => repo.GetOrganizer(organizerId)).Returns(existingOrganizer); + _userRepositoryMock.Setup(repo => repo.Update(organizerId, It.IsAny())).Returns(true); // Act & Assert Assert.Throws>(() => _userService.UpdateUser(organizerId, updateDto)); @@ -259,16 +260,17 @@ public void UpdateUser_ShouldReturnTrue_WhenModeratorIsUpdatedSuccessfully() Id = moderatorId }; - var existingModerator = new Moderator + var existingModerator = new User { Id = moderatorId, Email = "john.doe@example.com", CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow + UpdatedAt = DateTime.UtcNow, + Role = UserRole.Moderator }; - _moderatorRepositoryMock.Setup(repo => repo.Get(moderatorId)).Returns(existingModerator); // Simulate moderator found - _moderatorRepositoryMock.Setup(repo => repo.Update(moderatorId, It.IsAny())).Returns(true); + _userRepositoryMock.Setup(repo => repo.GetModerator(moderatorId)).Returns(existingModerator); // Simulate moderator found + _userRepositoryMock.Setup(repo => repo.Update(moderatorId, It.IsAny())).Returns(true); // Act var result = _userService.UpdateUser(moderatorId, updateDto); @@ -276,6 +278,6 @@ public void UpdateUser_ShouldReturnTrue_WhenModeratorIsUpdatedSuccessfully() // Assert result.Should().BeTrue(); - _moderatorRepositoryMock.Verify(repo => repo.Update(moderatorId, It.IsAny()), Times.Once); + _userRepositoryMock.Verify(repo => repo.Update(moderatorId, It.IsAny()), Times.Once); } } From 96dbc4592565a85e7c28076a4fde65dad3301de8 Mon Sep 17 00:00:00 2001 From: superjekk Date: Sun, 28 Dec 2025 13:42:44 -0500 Subject: [PATCH 16/18] TEST FIX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les tests qui étaient brisés par les modifications ont été réparés --- core/Controllers/DraftEventsController.cs | 6 +++--- core/Controllers/MeController.cs | 5 +++-- core/Controllers/ModeratorEventsController.cs | 5 +++-- core/Controllers/OrganizerEventsController.cs | 10 +++++----- core/Extensions/DependencyInjectionExtension.cs | 3 +++ core/Misc/IJwtUtils.cs | 6 ++++++ core/Misc/JwtUtils.cs | 4 ++-- core/Services/Abstractions/IUserService.cs | 4 ++-- core/Services/UserService.cs | 14 ++++++++------ tests/Tests/Services/UserServiceTests.cs | 15 +++++++++++++-- 10 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 core/Misc/IJwtUtils.cs diff --git a/core/Controllers/DraftEventsController.cs b/core/Controllers/DraftEventsController.cs index c4cd30c..b5e038b 100644 --- a/core/Controllers/DraftEventsController.cs +++ b/core/Controllers/DraftEventsController.cs @@ -28,7 +28,7 @@ namespace api.core.Controllers; [ApiController] [Authorize(Policy = AuthPolicies.OrganizerIsActive)] [Route("api/organizer-drafts")] // TODO : Change route to /api/me/drafts -public class DraftEventsController(ILogger logger, IDraftEventService draftService) : ControllerBase +public class DraftEventsController(ILogger logger, IDraftEventService draftService, IJwtUtils jwtUtils) : ControllerBase { /// /// Add a draft event to the database. This event will be saved as a draft and will not be visible @@ -41,7 +41,7 @@ public IActionResult AddDraft([FromForm] DraftEventRequestDTO draftEvent) { logger.LogInformation($"Adding new draft"); - var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); + var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); var evnt = draftService.AddDraftEvent(userId, draftEvent); return new OkObjectResult( @@ -61,7 +61,7 @@ public IActionResult AddDraft([FromForm] DraftEventRequestDTO draftEvent) [HttpPatch("{id}")] // TODO: Change this to a HttpPut instead public IActionResult UpdateDraft(Guid id, [FromForm] DraftEventRequestDTO draftEvent) { - var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); + var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); return draftService.UpdateDraftEvent(userId, id, draftEvent) ? Ok() : BadRequest(); } } diff --git a/core/Controllers/MeController.cs b/core/Controllers/MeController.cs index 72ec7fe..ea49421 100644 --- a/core/Controllers/MeController.cs +++ b/core/Controllers/MeController.cs @@ -1,4 +1,4 @@ -using api.core.Data; +using api.core.Data; using api.core.Data.requests; using api.core.Data.Responses; using api.core.Misc; @@ -18,10 +18,11 @@ namespace api.core.controllers; /// your JWT token. /// /// The User Service will allows managing your user's data +/// The JWT Utils allow to retrieve the ID from the user [Authorize] [ApiController] [Route("api/me")] -public class MeController(IUserService userService) : ControllerBase +public class MeController(IUserService userService, IJwtUtils jwtUtils) : ControllerBase { /// /// Get the user connected to the API using the JWT token diff --git a/core/Controllers/ModeratorEventsController.cs b/core/Controllers/ModeratorEventsController.cs index f0d7830..6e1e4ef 100644 --- a/core/Controllers/ModeratorEventsController.cs +++ b/core/Controllers/ModeratorEventsController.cs @@ -25,7 +25,8 @@ namespace api.core.Controllers; public class ModeratorEventsController( ILogger logger, IEventService eventService, - IUserService userService) : ControllerBase + IUserService userService, + IJwtUtils jwtUtils) : ControllerBase { /// /// Update the state of an event. This is used for a moderator that needs to @@ -41,7 +42,7 @@ public class ModeratorEventsController( [HttpPatch("{id}/state")] public IActionResult UpdateEventState(Guid id, [FromQuery] State newState, [FromQuery] string? reason) { - var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); + var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); return eventService.UpdateEventState(userId, id, newState, reason) ? Ok() : BadRequest(); } diff --git a/core/Controllers/OrganizerEventsController.cs b/core/Controllers/OrganizerEventsController.cs index 0777c7c..1010934 100644 --- a/core/Controllers/OrganizerEventsController.cs +++ b/core/Controllers/OrganizerEventsController.cs @@ -20,7 +20,7 @@ namespace api.core.Controllers; [ApiController] [Authorize(Policy = AuthPolicies.OrganizerIsActive)] [Route("api/organizer-events")] -public class OrganizerEventsController(ILogger logger, IEventService eventService) : ControllerBase +public class OrganizerEventsController(ILogger logger, IEventService eventService, IJwtUtils jwtUtils) : ControllerBase { /// /// Fetch events for the currently connected organizer @@ -46,7 +46,7 @@ public IActionResult MyEvents( [FromQuery] State state = State.All ) { - var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); + var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); logger.LogInformation("Getting events"); var validFilter = new PaginationRequest(pagination.PageNumber, pagination.PageSize); @@ -72,7 +72,7 @@ public IActionResult AddEvent([FromForm] EventCreationRequestDTO dto) { logger.LogInformation($"Adding new event"); - var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); + var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); var evnt = eventService.AddEvent(userId, dto); return new OkObjectResult( @@ -85,7 +85,7 @@ public IActionResult AddEvent([FromForm] EventCreationRequestDTO dto) [HttpDelete("{id}")] public IActionResult DeleteEvent(Guid id) { - var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); + var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); var isDeleted = eventService.DeleteEvent(userId, id); return isDeleted ? Ok() : BadRequest(); } @@ -93,7 +93,7 @@ public IActionResult DeleteEvent(Guid id) [HttpPatch("{id}")] public IActionResult UpdateEvent(Guid id, [FromForm] EventUpdateRequestDTO dto) { - var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); + var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); return eventService.UpdateEvent(userId, id, dto) ? Ok() : BadRequest(); } } diff --git a/core/Extensions/DependencyInjectionExtension.cs b/core/Extensions/DependencyInjectionExtension.cs index 7756238..a3e52ed 100644 --- a/core/Extensions/DependencyInjectionExtension.cs +++ b/core/Extensions/DependencyInjectionExtension.cs @@ -42,6 +42,9 @@ public static IServiceCollection AddDependencyInjection(this IServiceCollection services.AddTransient(); services.AddTransient(); + // Utils + services.AddTransient(); + return services; } diff --git a/core/Misc/IJwtUtils.cs b/core/Misc/IJwtUtils.cs new file mode 100644 index 0000000..2b04b93 --- /dev/null +++ b/core/Misc/IJwtUtils.cs @@ -0,0 +1,6 @@ +namespace api.core.Misc; + +public interface IJwtUtils +{ + string GetUserIdFromAuthHeader(string authHeader); +} \ No newline at end of file diff --git a/core/Misc/JwtUtils.cs b/core/Misc/JwtUtils.cs index 5119808..b4ebe5c 100644 --- a/core/Misc/JwtUtils.cs +++ b/core/Misc/JwtUtils.cs @@ -2,9 +2,9 @@ namespace api.core.Misc; -public static class JwtUtils +public class JwtUtils : IJwtUtils { - public static string GetUserIdFromAuthHeader(string authHeader) + public string GetUserIdFromAuthHeader(string authHeader) { var token = authHeader.Replace("Bearer ", ""); var handler = new JwtSecurityTokenHandler(); diff --git a/core/Services/Abstractions/IUserService.cs b/core/Services/Abstractions/IUserService.cs index a729787..c487b3e 100644 --- a/core/Services/Abstractions/IUserService.cs +++ b/core/Services/Abstractions/IUserService.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.Tracing; +using System.Diagnostics.Tracing; using api.core.Data.Enums; using api.core.Data.requests; @@ -10,7 +10,7 @@ public interface IUserService { public UserResponseDTO AddOrganizer(string id, UserCreateDTO organizerDto); - public UserResponseDTO GetUser(string id); + public UserResponseDTO GetUser(string userToken); public string GetUserAvatarUrl(string id); diff --git a/core/Services/UserService.cs b/core/Services/UserService.cs index 66c7cbe..7b56b99 100644 --- a/core/Services/UserService.cs +++ b/core/Services/UserService.cs @@ -20,7 +20,8 @@ public class UserService( IFileShareService fileShareService, ITagRepository tagRepository, IActivityAreaRepository activityAreaRepository, - IImageService imageService) : IUserService + IImageService imageService, + IJwtUtils jwtUtils) : IUserService { private const string AVATAR_FILE_NAME = "avatar.webp"; @@ -53,23 +54,24 @@ public UserResponseDTO AddOrganizer(string id, UserCreateDTO organizerDto) return user; } - public UserResponseDTO GetUser(string id) + public UserResponseDTO GetUser(string authHeader) { + string userId = jwtUtils.GetUserIdFromAuthHeader(authHeader); UserResponseDTO? userRes = null; - var organizer = organizerRepository.Get(id); + var organizer = userRepository.GetOrganizer(userId); if (organizer != null) userRes = UserResponseDTO.Map(organizer!); - var moderator = moderatorRepository.Get(id); + var moderator = userRepository.GetModerator(userId); if (moderator != null) userRes = UserResponseDTO.Map(moderator!); if (userRes == null) throw new Exception("No users associated with this ID"); - var fields = tagRepository.GetInterestFieldsForOrganizer(id); + var fields = tagRepository.GetInterestFieldsForOrganizer(userId); userRes.FieldsOfInterests = fields; - var avatarUri = fileShareService.FileGetDownloadUri($"{id}/{AVATAR_FILE_NAME}"); + var avatarUri = fileShareService.FileGetDownloadUri($"{userId}/{AVATAR_FILE_NAME}"); userRes.AvatarUrl = avatarUri.ToString(); return userRes; diff --git a/tests/Tests/Services/UserServiceTests.cs b/tests/Tests/Services/UserServiceTests.cs index 4dd4871..0e7ce00 100644 --- a/tests/Tests/Services/UserServiceTests.cs +++ b/tests/Tests/Services/UserServiceTests.cs @@ -13,6 +13,8 @@ using api.core.services.abstractions; using api.core.Data.Entities; using api.core.Repositories.Abstractions; +using Microsoft.IdentityModel.JsonWebTokens; +using api.core.Misc; namespace api.tests.Tests.Services; public class UserServiceTests @@ -22,6 +24,7 @@ public class UserServiceTests private readonly Mock _tagRepositoryMock; private readonly Mock _fileShareServiceMock; private readonly Mock _imageServiceMock; + private readonly Mock _jwtUtilsMock; private readonly UserService _userService; public UserServiceTests() @@ -31,6 +34,7 @@ public UserServiceTests() _activityAreaRepositoryMock = new Mock(); _fileShareServiceMock = new Mock(); _imageServiceMock = new Mock(); + _jwtUtilsMock = new Mock(); _fileShareServiceMock.Setup(service => service.FileGetDownloadUri(It.IsAny())).Returns(new Uri("http://example.com/avatar.webp")); _userService = new UserService( @@ -38,7 +42,8 @@ public UserServiceTests() _fileShareServiceMock.Object, _tagRepositoryMock.Object, _activityAreaRepositoryMock.Object, - _imageServiceMock.Object); + _imageServiceMock.Object, + _jwtUtilsMock.Object); } [Fact] @@ -70,7 +75,6 @@ public void AddOrganizer_ShouldReturnUserResponseDTO_WhenOrganizerIsAddedSuccess }; _activityAreaRepositoryMock.Setup(repo => repo.Get(It.IsAny())).Returns(activity); - _userRepositoryMock.Setup(repo => repo.Add(It.IsAny())).Returns(organizer); // Act @@ -109,6 +113,7 @@ public void GetUser_ShouldReturnUserResponseDTO_WhenOrganizerIsFoundById() }; _userRepositoryMock.Setup(repo => repo.GetOrganizer(organizerId)).Returns(organizer); + _jwtUtilsMock.Setup(jwtUtils => jwtUtils.GetUserIdFromAuthHeader(organizerId)).Returns(organizerId); // Act var result = _userService.GetUser(organizerId); @@ -135,6 +140,7 @@ public void GetUser_ShouldReturnUserResponseDTO_WhenModeratorIsFoundById() Role = UserRole.Moderator }; + _jwtUtilsMock.Setup(jwtUtil => jwtUtil.GetUserIdFromAuthHeader(moderatorId)).Returns(moderatorId); _userRepositoryMock.Setup(repo => repo.GetOrganizer(moderatorId)).Returns((User?)null); // Simulate no organizer found _userRepositoryMock.Setup(repo => repo.GetModerator(moderatorId)).Returns(moderator); // Simulate moderator found @@ -148,6 +154,7 @@ public void GetUser_ShouldReturnUserResponseDTO_WhenModeratorIsFoundById() _userRepositoryMock.Verify(repo => repo.GetOrganizer(moderatorId), Times.Once); _userRepositoryMock.Verify(repo => repo.GetModerator(moderatorId), Times.Once); + _jwtUtilsMock.Verify(jwtUtil => jwtUtil.GetUserIdFromAuthHeader(moderatorId), Times.Once); } [Fact] @@ -159,6 +166,7 @@ public void GetUser_ShouldThrowException_WhenNoUserIsAssociatedWithProvidedId() // Setup both organizer and moderator repositories to return null, simulating that no user is found with the provided ID _userRepositoryMock.Setup(repo => repo.GetOrganizer(userId)).Returns(null as User); _userRepositoryMock.Setup(repo => repo.GetModerator(userId)).Returns(null as User); + _jwtUtilsMock.Setup(jwtUtil => jwtUtil.GetUserIdFromAuthHeader(userId)).Returns(userId); // Act Action act = () => _userService.GetUser(userId); @@ -203,6 +211,7 @@ public void UpdateUser_ShouldReturnTrue_WhenOrganizerIsUpdatedSuccessfully() _activityAreaRepositoryMock.Setup(repo => repo.Get(actAreaIdModified)).Returns(activity); // Simulate activity area found _userRepositoryMock.Setup(repo => repo.GetOrganizer(organizerId)).Returns(existingOrganizer); _userRepositoryMock.Setup(repo => repo.Update(organizerId, It.IsAny())).Returns(true); + _jwtUtilsMock.Setup(jwtUtils => jwtUtils.GetUserIdFromAuthHeader(organizerId)).Returns(organizerId); // Act var result = _userService.UpdateUser(organizerId, updateDto); @@ -243,6 +252,7 @@ public void UpdateUser_ShouldThrow_WhenActivityAreaIsNotFoundInTheList() _activityAreaRepositoryMock.Setup(repo => repo.Get(badActAreaIdModified)).Returns(null as ActivityArea); // Simulate activity area not found _userRepositoryMock.Setup(repo => repo.GetOrganizer(organizerId)).Returns(existingOrganizer); _userRepositoryMock.Setup(repo => repo.Update(organizerId, It.IsAny())).Returns(true); + _jwtUtilsMock.Setup(jwtUtils => jwtUtils.GetUserIdFromAuthHeader(organizerId)).Returns(organizerId); // Act & Assert Assert.Throws>(() => _userService.UpdateUser(organizerId, updateDto)); @@ -271,6 +281,7 @@ public void UpdateUser_ShouldReturnTrue_WhenModeratorIsUpdatedSuccessfully() _userRepositoryMock.Setup(repo => repo.GetModerator(moderatorId)).Returns(existingModerator); // Simulate moderator found _userRepositoryMock.Setup(repo => repo.Update(moderatorId, It.IsAny())).Returns(true); + _jwtUtilsMock.Setup(jwtUtils => jwtUtils.GetUserIdFromAuthHeader(moderatorId)).Returns(moderatorId); // Act var result = _userService.UpdateUser(moderatorId, updateDto); From 49db8da511b21a55842ef209fab0107ebefaa677 Mon Sep 17 00:00:00 2001 From: superjekk Date: Mon, 29 Dec 2025 23:07:23 -0500 Subject: [PATCH 17/18] FCT : Migration des utilisateurs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les tables d'utilisateurs sont maintenant ensemble conformément à l'Issue --- core/Data/Entities/ActivityArea.cs | 14 +- core/Data/Entities/Publication.cs | 2 - core/Data/Entities/User.cs | 17 +- core/Data/EventManagementContext.cs | 10 - .../20251219194542_UserIdTypeReplacement.cs | 105 -------- ...s => 20251230035918_UserMerge.Designer.cs} | 238 +++++++---------- core/Migrations/20251230035918_UserMerge.cs | 251 ++++++++++++++++++ .../EventManagementContextModelSnapshot.cs | 234 +++++++--------- 8 files changed, 458 insertions(+), 413 deletions(-) delete mode 100644 core/Migrations/20251219194542_UserIdTypeReplacement.cs rename core/Migrations/{20251219194542_UserIdTypeReplacement.Designer.cs => 20251230035918_UserMerge.Designer.cs} (86%) create mode 100644 core/Migrations/20251230035918_UserMerge.cs diff --git a/core/Data/Entities/ActivityArea.cs b/core/Data/Entities/ActivityArea.cs index a268ba8..8bdaf7b 100644 --- a/core/Data/Entities/ActivityArea.cs +++ b/core/Data/Entities/ActivityArea.cs @@ -12,9 +12,15 @@ public partial class ActivityArea : BaseEntity public string NameEn { get; set; } = null!; - //[InverseProperty("ActivityArea")] - public virtual ICollection Organizers { get; set; } = new List(); + /// + /// Groups all Users, no matter their role + /// + [InverseProperty(nameof(User.ActivityArea))] + public virtual ICollection Users { get; set; } = new List(); - //[InverseProperty("ActivityArea")] - public virtual ICollection Moderators { get; set; } = new List(); + [NotMapped] + public ICollection Organizers => Users.Where(u => u.Role.HasFlag(UserRole.Organizer)).ToList(); + + [NotMapped] + public ICollection Moderators => Users.Where(u => u.Role.HasFlag(UserRole.Moderator)).ToList(); } diff --git a/core/Data/Entities/Publication.cs b/core/Data/Entities/Publication.cs index c23900d..7f9fb79 100644 --- a/core/Data/Entities/Publication.cs +++ b/core/Data/Entities/Publication.cs @@ -51,11 +51,9 @@ public partial class Publication public virtual Event? Event { get; set; } [ForeignKey("ModeratorId")] - [InverseProperty("Publications")] public virtual User? Moderator { get; set; } [ForeignKey("OrganizerId")] - [InverseProperty("Publications")] public virtual User Organizer { get; set; } = null!; [InverseProperty("Publication")] diff --git a/core/Data/Entities/User.cs b/core/Data/Entities/User.cs index afd085a..3886b7f 100644 --- a/core/Data/Entities/User.cs +++ b/core/Data/Entities/User.cs @@ -49,20 +49,25 @@ public class User public string? WebSiteLink { get; set; } - [ForeignKey("ActivityAreaId")] + [ForeignKey(nameof(ActivityAreaId))] + [InverseProperty(nameof(ActivityArea.Users))] public virtual ActivityArea? ActivityArea { get; set; } [InverseProperty(nameof(Subscription.Organizer))] public virtual ICollection Subscriptions { get; set; } = new List(); - // Adaptation personalisée - public virtual ICollection Publications { get; set; } = new List(); + // Il ne semble pas nécessaire de mapper les publications dans le User pour le moment + //public virtual ICollection Publications { get; set; } = new List(); } +/// +/// Specifies the roles that a user can have within the system. +/// +/// This enumeration supports bitwise combination of its member values. A User therefore can have multiple roles. [Flags] public enum UserRole { - Admin = 0b00000001, - Moderator = 0b00000010, - Organizer = 0b00000100 + Admin = 0b10000000, + Moderator = 0b01000000, + Organizer = 0b00100000 } \ No newline at end of file diff --git a/core/Data/EventManagementContext.cs b/core/Data/EventManagementContext.cs index 457bd6a..494776a 100644 --- a/core/Data/EventManagementContext.cs +++ b/core/Data/EventManagementContext.cs @@ -46,12 +46,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.Id).ValueGeneratedNever(); }); - //modelBuilder.Entity(entity => - //{ - // entity.Property(e => e.CreatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); - // entity.Property(e => e.UpdatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); - //}); - modelBuilder.Entity(entity => { entity.Property(e => e.CreatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); @@ -64,10 +58,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.CreatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); entity.Property(e => e.UpdatedAt).HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); - entity.HasOne(d => d.Moderator).WithMany(p => p.Publications).HasConstraintName("Publication_ModeratorId_fkey"); - - entity.HasOne(d => d.Organizer).WithMany(p => p.Publications).HasConstraintName("Publication_OrganizerId_fkey"); - entity.HasMany(d => d.Tags).WithMany(p => p.Publications) .UsingEntity>( "PublicationTag", diff --git a/core/Migrations/20251219194542_UserIdTypeReplacement.cs b/core/Migrations/20251219194542_UserIdTypeReplacement.cs deleted file mode 100644 index 83b4077..0000000 --- a/core/Migrations/20251219194542_UserIdTypeReplacement.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace api.core.Migrations -{ - /// - public partial class UserIdTypeReplacement : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "OrganizerId", - table: "Subscription", - type: "text", - nullable: false, - oldClrType: typeof(Guid), - oldType: "uuid"); - - migrationBuilder.AlterColumn( - name: "OrganizerId", - table: "Publication", - type: "text", - nullable: false, - oldClrType: typeof(Guid), - oldType: "uuid"); - - migrationBuilder.AlterColumn( - name: "ModeratorId", - table: "Publication", - type: "text", - nullable: true, - oldClrType: typeof(Guid), - oldType: "uuid", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Organizer", - type: "text", - nullable: false, - oldClrType: typeof(Guid), - oldType: "uuid", - oldDefaultValueSql: "gen_random_uuid()"); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Moderator", - type: "text", - nullable: false, - oldClrType: typeof(Guid), - oldType: "uuid", - oldDefaultValueSql: "gen_random_uuid()"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "OrganizerId", - table: "Subscription", - type: "uuid", - nullable: false, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "OrganizerId", - table: "Publication", - type: "uuid", - nullable: false, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "ModeratorId", - table: "Publication", - type: "uuid", - nullable: true, - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Organizer", - type: "uuid", - nullable: false, - defaultValueSql: "gen_random_uuid()", - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Id", - table: "Moderator", - type: "uuid", - nullable: false, - defaultValueSql: "gen_random_uuid()", - oldClrType: typeof(string), - oldType: "text"); - } - } -} diff --git a/core/Migrations/20251219194542_UserIdTypeReplacement.Designer.cs b/core/Migrations/20251230035918_UserMerge.Designer.cs similarity index 86% rename from core/Migrations/20251219194542_UserIdTypeReplacement.Designer.cs rename to core/Migrations/20251230035918_UserMerge.Designer.cs index 496dadf..664c69b 100644 --- a/core/Migrations/20251219194542_UserIdTypeReplacement.Designer.cs +++ b/core/Migrations/20251230035918_UserMerge.Designer.cs @@ -12,8 +12,8 @@ namespace api.core.Migrations { [DbContext(typeof(EventManagementContext))] - [Migration("20251219194542_UserIdTypeReplacement")] - partial class UserIdTypeReplacement + [Migration("20251230035918_UserMerge")] + partial class UserMerge { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -68,69 +68,60 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("TagsHierarchy", (string)null); }); - modelBuilder.Entity("api.core.data.entities.ActivityArea", b => + modelBuilder.Entity("api.core.Data.Entities.User", b => { - b.Property("Id") + b.Property("Id") .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ActivityAreaId") .HasColumnType("uuid"); b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); b.Property("DeletedAt") .HasColumnType("timestamp with time zone"); - b.Property("NameEn") - .IsRequired() + b.Property("DiscordLink") .HasColumnType("text"); - b.Property("NameFr") + b.Property("Email") .IsRequired() .HasColumnType("text"); - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("ActivityArea"); - }); - - modelBuilder.Entity("api.core.data.entities.Event", b => - { - b.Property("Id") - .HasColumnType("uuid"); + b.Property("FacebookLink") + .HasColumnType("text"); - b.Property("EventEndDate") - .HasColumnType("timestamp with time zone"); + b.Property("HasLoggedIn") + .HasColumnType("boolean"); - b.Property("EventStartDate") - .HasColumnType("timestamp with time zone"); + b.Property("InstagramLink") + .HasColumnType("text"); - b.HasKey("Id"); + b.Property("IsActive") + .HasColumnType("boolean"); - b.ToTable("Event"); - }); + b.Property("LinkedInLink") + .HasColumnType("text"); - modelBuilder.Entity("api.core.data.entities.Moderator", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("Organization") + .IsRequired() .HasColumnType("text"); - b.Property("ActivityAreaId") - .HasColumnType("uuid"); + b.Property("ProfileDescription") + .IsRequired() + .HasColumnType("text"); - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + b.Property("RedditLink") + .HasColumnType("text"); - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone"); + b.Property("Role") + .HasColumnType("integer"); - b.Property("Email") - .IsRequired() + b.Property("TikTokLink") .HasColumnType("text"); b.Property("UpdatedAt") @@ -138,14 +129,20 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("timestamp with time zone") .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + b.Property("WebSiteLink") + .HasColumnType("text"); + + b.Property("XLink") + .HasColumnType("text"); + b.HasKey("Id"); b.HasIndex("ActivityAreaId"); - b.ToTable("Moderator"); + b.ToTable("User"); }); - modelBuilder.Entity("api.core.data.entities.Notification", b => + modelBuilder.Entity("api.core.data.entities.ActivityArea", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -157,96 +154,69 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("DeletedAt") .HasColumnType("timestamp with time zone"); - b.Property("IsSent") - .HasColumnType("boolean"); - - b.Property("PublicationId") - .HasColumnType("uuid"); + b.Property("NameEn") + .IsRequired() + .HasColumnType("text"); - b.Property("SubscriptionId") - .HasColumnType("uuid"); + b.Property("NameFr") + .IsRequired() + .HasColumnType("text"); b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); b.HasKey("Id"); - b.HasIndex("PublicationId"); - - b.HasIndex("SubscriptionId"); - - b.ToTable("Notification"); + b.ToTable("ActivityArea"); }); - modelBuilder.Entity("api.core.data.entities.Organizer", b => + modelBuilder.Entity("api.core.data.entities.Event", b => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("text"); - - b.Property("ActivityAreaId") + b.Property("Id") .HasColumnType("uuid"); - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + b.Property("EventEndDate") + .HasColumnType("timestamp with time zone"); - b.Property("DeletedAt") + b.Property("EventStartDate") .HasColumnType("timestamp with time zone"); - b.Property("DiscordLink") - .HasColumnType("text"); + b.HasKey("Id"); - b.Property("Email") - .IsRequired() - .HasColumnType("text"); + b.ToTable("Event"); + }); - b.Property("FacebookLink") - .HasColumnType("text"); + modelBuilder.Entity("api.core.data.entities.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); - b.Property("HasLoggedIn") - .HasColumnType("boolean"); + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); - b.Property("InstagramLink") - .HasColumnType("text"); + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); - b.Property("IsActive") + b.Property("IsSent") .HasColumnType("boolean"); - b.Property("LinkedInLink") - .HasColumnType("text"); - - b.Property("Organization") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProfileDescription") - .IsRequired() - .HasColumnType("text"); - - b.Property("RedditLink") - .HasColumnType("text"); + b.Property("PublicationId") + .HasColumnType("uuid"); - b.Property("TikTokLink") - .HasColumnType("text"); + b.Property("SubscriptionId") + .HasColumnType("uuid"); b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); - - b.Property("WebSiteLink") - .HasColumnType("text"); - - b.Property("XLink") - .HasColumnType("text"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); - b.HasIndex("ActivityAreaId"); + b.HasIndex("PublicationId"); - b.ToTable("Organizer"); + b.HasIndex("SubscriptionId"); + + b.ToTable("Notification"); }); modelBuilder.Entity("api.core.data.entities.Publication", b => @@ -448,6 +418,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("api.core.Data.Entities.User", b => + { + b.HasOne("api.core.data.entities.ActivityArea", "ActivityArea") + .WithMany("Users") + .HasForeignKey("ActivityAreaId"); + + b.Navigation("ActivityArea"); + }); + modelBuilder.Entity("api.core.data.entities.Event", b => { b.HasOne("api.core.data.entities.Publication", "Publication") @@ -459,15 +438,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Publication"); }); - modelBuilder.Entity("api.core.data.entities.Moderator", b => - { - b.HasOne("api.core.data.entities.ActivityArea", "ActivityArea") - .WithMany("Moderators") - .HasForeignKey("ActivityAreaId"); - - b.Navigation("ActivityArea"); - }); - modelBuilder.Entity("api.core.data.entities.Notification", b => { b.HasOne("api.core.data.entities.Publication", "Publication") @@ -487,28 +457,17 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Subscription"); }); - modelBuilder.Entity("api.core.data.entities.Organizer", b => - { - b.HasOne("api.core.data.entities.ActivityArea", "ActivityArea") - .WithMany("Organizers") - .HasForeignKey("ActivityAreaId"); - - b.Navigation("ActivityArea"); - }); - modelBuilder.Entity("api.core.data.entities.Publication", b => { - b.HasOne("api.core.data.entities.Moderator", "Moderator") - .WithMany("Publications") - .HasForeignKey("ModeratorId") - .HasConstraintName("Publication_ModeratorId_fkey"); + b.HasOne("api.core.Data.Entities.User", "Moderator") + .WithMany() + .HasForeignKey("ModeratorId"); - b.HasOne("api.core.data.entities.Organizer", "Organizer") - .WithMany("Publications") + b.HasOne("api.core.Data.Entities.User", "Organizer") + .WithMany() .HasForeignKey("OrganizerId") .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("Publication_OrganizerId_fkey"); + .IsRequired(); b.Navigation("Moderator"); @@ -529,7 +488,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("api.core.data.entities.Subscription", b => { - b.HasOne("api.core.data.entities.Organizer", "Organizer") + b.HasOne("api.core.Data.Entities.User", "Organizer") .WithMany("Subscriptions") .HasForeignKey("OrganizerId") .OnDelete(DeleteBehavior.Cascade) @@ -538,23 +497,14 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Organizer"); }); - modelBuilder.Entity("api.core.data.entities.ActivityArea", b => - { - b.Navigation("Moderators"); - - b.Navigation("Organizers"); - }); - - modelBuilder.Entity("api.core.data.entities.Moderator", b => + modelBuilder.Entity("api.core.Data.Entities.User", b => { - b.Navigation("Publications"); + b.Navigation("Subscriptions"); }); - modelBuilder.Entity("api.core.data.entities.Organizer", b => + modelBuilder.Entity("api.core.data.entities.ActivityArea", b => { - b.Navigation("Publications"); - - b.Navigation("Subscriptions"); + b.Navigation("Users"); }); modelBuilder.Entity("api.core.data.entities.Publication", b => diff --git a/core/Migrations/20251230035918_UserMerge.cs b/core/Migrations/20251230035918_UserMerge.cs new file mode 100644 index 0000000..fb24f62 --- /dev/null +++ b/core/Migrations/20251230035918_UserMerge.cs @@ -0,0 +1,251 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace api.core.Migrations +{ + /// + public partial class UserMerge : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "Publication_ModeratorId_fkey", + table: "Publication"); + + migrationBuilder.DropForeignKey( + name: "Publication_OrganizerId_fkey", + table: "Publication"); + + migrationBuilder.DropForeignKey( + name: "FK_Subscription_Organizer_OrganizerId", + table: "Subscription"); + + migrationBuilder.DropTable( + name: "Moderator"); + + migrationBuilder.DropTable( + name: "Organizer"); + + migrationBuilder.AlterColumn( + name: "OrganizerId", + table: "Subscription", + type: "text", + nullable: false, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "OrganizerId", + table: "Publication", + type: "text", + nullable: false, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "ModeratorId", + table: "Publication", + type: "text", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.CreateTable( + name: "User", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Email = table.Column(type: "text", nullable: false), + ActivityAreaId = table.Column(type: "uuid", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "(now() AT TIME ZONE 'utc'::text)"), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "(now() AT TIME ZONE 'utc'::text)"), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + Role = table.Column(type: "integer", nullable: false), + Organization = table.Column(type: "text", nullable: false), + ProfileDescription = table.Column(type: "text", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false), + HasLoggedIn = table.Column(type: "boolean", nullable: false), + FacebookLink = table.Column(type: "text", nullable: true), + InstagramLink = table.Column(type: "text", nullable: true), + TikTokLink = table.Column(type: "text", nullable: true), + XLink = table.Column(type: "text", nullable: true), + DiscordLink = table.Column(type: "text", nullable: true), + LinkedInLink = table.Column(type: "text", nullable: true), + RedditLink = table.Column(type: "text", nullable: true), + WebSiteLink = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_User", x => x.Id); + table.ForeignKey( + name: "FK_User_ActivityArea_ActivityAreaId", + column: x => x.ActivityAreaId, + principalTable: "ActivityArea", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_User_ActivityAreaId", + table: "User", + column: "ActivityAreaId"); + + migrationBuilder.AddForeignKey( + name: "FK_Publication_User_ModeratorId", + table: "Publication", + column: "ModeratorId", + principalTable: "User", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_Publication_User_OrganizerId", + table: "Publication", + column: "OrganizerId", + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Subscription_User_OrganizerId", + table: "Subscription", + column: "OrganizerId", + principalTable: "User", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Publication_User_ModeratorId", + table: "Publication"); + + migrationBuilder.DropForeignKey( + name: "FK_Publication_User_OrganizerId", + table: "Publication"); + + migrationBuilder.DropForeignKey( + name: "FK_Subscription_User_OrganizerId", + table: "Subscription"); + + migrationBuilder.DropTable( + name: "User"); + + migrationBuilder.AlterColumn( + name: "OrganizerId", + table: "Subscription", + type: "uuid", + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn( + name: "OrganizerId", + table: "Publication", + type: "uuid", + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn( + name: "ModeratorId", + table: "Publication", + type: "uuid", + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.CreateTable( + name: "Moderator", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + ActivityAreaId = table.Column(type: "uuid", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "(now() AT TIME ZONE 'utc'::text)"), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + Email = table.Column(type: "text", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "(now() AT TIME ZONE 'utc'::text)") + }, + constraints: table => + { + table.PrimaryKey("PK_Moderator", x => x.Id); + table.ForeignKey( + name: "FK_Moderator_ActivityArea_ActivityAreaId", + column: x => x.ActivityAreaId, + principalTable: "ActivityArea", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "Organizer", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + ActivityAreaId = table.Column(type: "uuid", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "(now() AT TIME ZONE 'utc'::text)"), + DeletedAt = table.Column(type: "timestamp with time zone", nullable: true), + DiscordLink = table.Column(type: "text", nullable: true), + Email = table.Column(type: "text", nullable: false), + FacebookLink = table.Column(type: "text", nullable: true), + HasLoggedIn = table.Column(type: "boolean", nullable: false), + InstagramLink = table.Column(type: "text", nullable: true), + IsActive = table.Column(type: "boolean", nullable: false), + LinkedInLink = table.Column(type: "text", nullable: true), + Organization = table.Column(type: "text", nullable: false), + ProfileDescription = table.Column(type: "text", nullable: false), + RedditLink = table.Column(type: "text", nullable: true), + TikTokLink = table.Column(type: "text", nullable: true), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "(now() AT TIME ZONE 'utc'::text)"), + WebSiteLink = table.Column(type: "text", nullable: true), + XLink = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Organizer", x => x.Id); + table.ForeignKey( + name: "FK_Organizer_ActivityArea_ActivityAreaId", + column: x => x.ActivityAreaId, + principalTable: "ActivityArea", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_Moderator_ActivityAreaId", + table: "Moderator", + column: "ActivityAreaId"); + + migrationBuilder.CreateIndex( + name: "IX_Organizer_ActivityAreaId", + table: "Organizer", + column: "ActivityAreaId"); + + migrationBuilder.AddForeignKey( + name: "Publication_ModeratorId_fkey", + table: "Publication", + column: "ModeratorId", + principalTable: "Moderator", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "Publication_OrganizerId_fkey", + table: "Publication", + column: "OrganizerId", + principalTable: "Organizer", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Subscription_Organizer_OrganizerId", + table: "Subscription", + column: "OrganizerId", + principalTable: "Organizer", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/core/Migrations/EventManagementContextModelSnapshot.cs b/core/Migrations/EventManagementContextModelSnapshot.cs index 68c5e14..58e5b44 100644 --- a/core/Migrations/EventManagementContextModelSnapshot.cs +++ b/core/Migrations/EventManagementContextModelSnapshot.cs @@ -65,69 +65,60 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("TagsHierarchy", (string)null); }); - modelBuilder.Entity("api.core.data.entities.ActivityArea", b => + modelBuilder.Entity("api.core.Data.Entities.User", b => { - b.Property("Id") + b.Property("Id") .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ActivityAreaId") .HasColumnType("uuid"); b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); b.Property("DeletedAt") .HasColumnType("timestamp with time zone"); - b.Property("NameEn") - .IsRequired() + b.Property("DiscordLink") .HasColumnType("text"); - b.Property("NameFr") + b.Property("Email") .IsRequired() .HasColumnType("text"); - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("ActivityArea"); - }); - - modelBuilder.Entity("api.core.data.entities.Event", b => - { - b.Property("Id") - .HasColumnType("uuid"); + b.Property("FacebookLink") + .HasColumnType("text"); - b.Property("EventEndDate") - .HasColumnType("timestamp with time zone"); + b.Property("HasLoggedIn") + .HasColumnType("boolean"); - b.Property("EventStartDate") - .HasColumnType("timestamp with time zone"); + b.Property("InstagramLink") + .HasColumnType("text"); - b.HasKey("Id"); + b.Property("IsActive") + .HasColumnType("boolean"); - b.ToTable("Event"); - }); + b.Property("LinkedInLink") + .HasColumnType("text"); - modelBuilder.Entity("api.core.data.entities.Moderator", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() + b.Property("Organization") + .IsRequired() .HasColumnType("text"); - b.Property("ActivityAreaId") - .HasColumnType("uuid"); + b.Property("ProfileDescription") + .IsRequired() + .HasColumnType("text"); - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + b.Property("RedditLink") + .HasColumnType("text"); - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone"); + b.Property("Role") + .HasColumnType("integer"); - b.Property("Email") - .IsRequired() + b.Property("TikTokLink") .HasColumnType("text"); b.Property("UpdatedAt") @@ -135,14 +126,20 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("timestamp with time zone") .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + b.Property("WebSiteLink") + .HasColumnType("text"); + + b.Property("XLink") + .HasColumnType("text"); + b.HasKey("Id"); b.HasIndex("ActivityAreaId"); - b.ToTable("Moderator"); + b.ToTable("User"); }); - modelBuilder.Entity("api.core.data.entities.Notification", b => + modelBuilder.Entity("api.core.data.entities.ActivityArea", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -154,96 +151,69 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DeletedAt") .HasColumnType("timestamp with time zone"); - b.Property("IsSent") - .HasColumnType("boolean"); - - b.Property("PublicationId") - .HasColumnType("uuid"); + b.Property("NameEn") + .IsRequired() + .HasColumnType("text"); - b.Property("SubscriptionId") - .HasColumnType("uuid"); + b.Property("NameFr") + .IsRequired() + .HasColumnType("text"); b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); b.HasKey("Id"); - b.HasIndex("PublicationId"); - - b.HasIndex("SubscriptionId"); - - b.ToTable("Notification"); + b.ToTable("ActivityArea"); }); - modelBuilder.Entity("api.core.data.entities.Organizer", b => + modelBuilder.Entity("api.core.data.entities.Event", b => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("text"); - - b.Property("ActivityAreaId") + b.Property("Id") .HasColumnType("uuid"); - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); + b.Property("EventEndDate") + .HasColumnType("timestamp with time zone"); - b.Property("DeletedAt") + b.Property("EventStartDate") .HasColumnType("timestamp with time zone"); - b.Property("DiscordLink") - .HasColumnType("text"); + b.HasKey("Id"); - b.Property("Email") - .IsRequired() - .HasColumnType("text"); + b.ToTable("Event"); + }); - b.Property("FacebookLink") - .HasColumnType("text"); + modelBuilder.Entity("api.core.data.entities.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); - b.Property("HasLoggedIn") - .HasColumnType("boolean"); + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); - b.Property("InstagramLink") - .HasColumnType("text"); + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); - b.Property("IsActive") + b.Property("IsSent") .HasColumnType("boolean"); - b.Property("LinkedInLink") - .HasColumnType("text"); - - b.Property("Organization") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProfileDescription") - .IsRequired() - .HasColumnType("text"); - - b.Property("RedditLink") - .HasColumnType("text"); + b.Property("PublicationId") + .HasColumnType("uuid"); - b.Property("TikTokLink") - .HasColumnType("text"); + b.Property("SubscriptionId") + .HasColumnType("uuid"); b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("(now() AT TIME ZONE 'utc'::text)"); - - b.Property("WebSiteLink") - .HasColumnType("text"); - - b.Property("XLink") - .HasColumnType("text"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); - b.HasIndex("ActivityAreaId"); + b.HasIndex("PublicationId"); - b.ToTable("Organizer"); + b.HasIndex("SubscriptionId"); + + b.ToTable("Notification"); }); modelBuilder.Entity("api.core.data.entities.Publication", b => @@ -445,6 +415,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("api.core.Data.Entities.User", b => + { + b.HasOne("api.core.data.entities.ActivityArea", "ActivityArea") + .WithMany("Users") + .HasForeignKey("ActivityAreaId"); + + b.Navigation("ActivityArea"); + }); + modelBuilder.Entity("api.core.data.entities.Event", b => { b.HasOne("api.core.data.entities.Publication", "Publication") @@ -456,15 +435,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Publication"); }); - modelBuilder.Entity("api.core.data.entities.Moderator", b => - { - b.HasOne("api.core.data.entities.ActivityArea", "ActivityArea") - .WithMany("Moderators") - .HasForeignKey("ActivityAreaId"); - - b.Navigation("ActivityArea"); - }); - modelBuilder.Entity("api.core.data.entities.Notification", b => { b.HasOne("api.core.data.entities.Publication", "Publication") @@ -484,28 +454,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Subscription"); }); - modelBuilder.Entity("api.core.data.entities.Organizer", b => - { - b.HasOne("api.core.data.entities.ActivityArea", "ActivityArea") - .WithMany("Organizers") - .HasForeignKey("ActivityAreaId"); - - b.Navigation("ActivityArea"); - }); - modelBuilder.Entity("api.core.data.entities.Publication", b => { - b.HasOne("api.core.data.entities.Moderator", "Moderator") - .WithMany("Publications") - .HasForeignKey("ModeratorId") - .HasConstraintName("Publication_ModeratorId_fkey"); + b.HasOne("api.core.Data.Entities.User", "Moderator") + .WithMany() + .HasForeignKey("ModeratorId"); - b.HasOne("api.core.data.entities.Organizer", "Organizer") - .WithMany("Publications") + b.HasOne("api.core.Data.Entities.User", "Organizer") + .WithMany() .HasForeignKey("OrganizerId") .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("Publication_OrganizerId_fkey"); + .IsRequired(); b.Navigation("Moderator"); @@ -526,7 +485,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("api.core.data.entities.Subscription", b => { - b.HasOne("api.core.data.entities.Organizer", "Organizer") + b.HasOne("api.core.Data.Entities.User", "Organizer") .WithMany("Subscriptions") .HasForeignKey("OrganizerId") .OnDelete(DeleteBehavior.Cascade) @@ -535,23 +494,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Organizer"); }); - modelBuilder.Entity("api.core.data.entities.ActivityArea", b => - { - b.Navigation("Moderators"); - - b.Navigation("Organizers"); - }); - - modelBuilder.Entity("api.core.data.entities.Moderator", b => + modelBuilder.Entity("api.core.Data.Entities.User", b => { - b.Navigation("Publications"); + b.Navigation("Subscriptions"); }); - modelBuilder.Entity("api.core.data.entities.Organizer", b => + modelBuilder.Entity("api.core.data.entities.ActivityArea", b => { - b.Navigation("Publications"); - - b.Navigation("Subscriptions"); + b.Navigation("Users"); }); modelBuilder.Entity("api.core.data.entities.Publication", b => From 25bc06cab96cb6ff655cf819474187e9954ec1e2 Mon Sep 17 00:00:00 2001 From: superjekk Date: Tue, 30 Dec 2025 20:32:41 -0500 Subject: [PATCH 18/18] BUGFIX : Oubli de commit --- core/Controllers/MeController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/Controllers/MeController.cs b/core/Controllers/MeController.cs index ea49421..b2a1c27 100644 --- a/core/Controllers/MeController.cs +++ b/core/Controllers/MeController.cs @@ -1,4 +1,4 @@ -using api.core.Data; +using api.core.Data; using api.core.Data.requests; using api.core.Data.Responses; using api.core.Misc; @@ -31,8 +31,8 @@ public class MeController(IUserService userService, IJwtUtils jwtUtils) : Contro [HttpGet] public IActionResult GetUser() { - var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); - var organizer = userService.GetUser(userId); + //var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers.Authorization!); + var organizer = userService.GetUser(Request.Headers.Authorization!); return new OkObjectResult( new Response @@ -49,7 +49,7 @@ public IActionResult GetUser() [HttpPatch] public IActionResult UpdateUser([FromBody] UserUpdateDTO user) { - var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); + var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); return userService.UpdateUser(userId, user) ? Ok() : BadRequest(); } @@ -61,7 +61,7 @@ public IActionResult UpdateUser([FromBody] UserUpdateDTO user) [HttpPatch("avatar")] public IActionResult UpdateUserAvatar([FromForm] UserAvatarUpdateDTO avatarReq) { - var userId = JwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); + var userId = jwtUtils.GetUserIdFromAuthHeader(HttpContext.Request.Headers["Authorization"]!); var url = userService.UpdateUserAvatar(userId, avatarReq.avatarFile); return new OkObjectResult(