From 7129e5679e3d73b490cfd88aec150f311f6c0d86 Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Thu, 12 Feb 2026 21:06:07 -0500 Subject: [PATCH] Update exception handling (#146) --- src/Core/API/API.Core/API.Core.csproj | 3 +- .../API.Core/Controllers/AuthController.cs | 9 +- .../API.Core/Controllers/UserController.cs | 1 - src/Core/API/API.Core/Dockerfile | 3 +- src/Core/API/API.Core/GlobalException.cs | 84 +++++++++++++++++++ .../ValidationExceptionHandlingMiddleware.cs | 47 ----------- src/Core/API/API.Core/Program.cs | 36 +++----- src/Core/API/API.Specs/Dockerfile | 3 +- .../API.Specs/Features/Registration.feature | 14 +--- src/Core/API/API.Specs/Steps/AuthSteps.cs | 12 --- src/Core/Core.slnx | 3 +- .../Database.Seed/Database.Seed.csproj | 2 +- src/Core/Database/Database.Seed/UserSeeder.cs | 3 +- .../Domain.Entities.csproj} | 0 .../Entities/UserAccount.cs | 0 .../Entities/UserCredential.cs | 0 .../Entities/UserVerification.cs | 0 .../Domain.Exceptions.csproj | 9 ++ src/Core/Domain.Exceptions/Exceptions.cs | 33 ++++++++ .../Dockerfile | 3 +- .../Infrastructure.Repository.csproj | 2 +- .../Service.Auth/Auth/ILoginService.cs | 2 +- .../Service/Service.Auth/Auth/LoginService.cs | 15 +++- .../Service.Auth/Auth/RegisterService.cs | 13 ++- .../Service/Service.Auth/Service.Auth.csproj | 6 +- .../Service.UserManagement.csproj | 4 +- .../User/IUserService.cs | 2 +- .../User/UserService.cs | 8 +- 28 files changed, 191 insertions(+), 126 deletions(-) create mode 100644 src/Core/API/API.Core/GlobalException.cs delete mode 100644 src/Core/API/API.Core/Middleware/ValidationExceptionHandlingMiddleware.cs rename src/Core/{Domain/Domain.csproj => Domain.Entities/Domain.Entities.csproj} (100%) rename src/Core/{Domain => Domain.Entities}/Entities/UserAccount.cs (100%) rename src/Core/{Domain => Domain.Entities}/Entities/UserCredential.cs (100%) rename src/Core/{Domain => Domain.Entities}/Entities/UserVerification.cs (100%) create mode 100644 src/Core/Domain.Exceptions/Domain.Exceptions.csproj create mode 100644 src/Core/Domain.Exceptions/Exceptions.cs diff --git a/src/Core/API/API.Core/API.Core.csproj b/src/Core/API/API.Core/API.Core.csproj index 29247b4..dbaf5e6 100644 --- a/src/Core/API/API.Core/API.Core.csproj +++ b/src/Core/API/API.Core/API.Core.csproj @@ -18,7 +18,8 @@ - + + diff --git a/src/Core/API/API.Core/Controllers/AuthController.cs b/src/Core/API/API.Core/Controllers/AuthController.cs index bccde9b..fb0ed13 100644 --- a/src/Core/API/API.Core/Controllers/AuthController.cs +++ b/src/Core/API/API.Core/Controllers/AuthController.cs @@ -44,13 +44,6 @@ namespace API.Core.Controllers public async Task Login([FromBody] LoginRequest req) { var userAccount = await login.LoginAsync(req.Username, req.Password); - if (userAccount is null) - { - return Unauthorized(new ResponseBody - { - Message = "Invalid username or password." - }); - } UserDTO dto = new(userAccount.UserAccountId, userAccount.Username); @@ -64,4 +57,4 @@ namespace API.Core.Controllers }); } } -} \ No newline at end of file +} diff --git a/src/Core/API/API.Core/Controllers/UserController.cs b/src/Core/API/API.Core/Controllers/UserController.cs index fe98481..3cdd55a 100644 --- a/src/Core/API/API.Core/Controllers/UserController.cs +++ b/src/Core/API/API.Core/Controllers/UserController.cs @@ -19,7 +19,6 @@ namespace API.Core.Controllers public async Task> GetById(Guid id) { var user = await userService.GetByIdAsync(id); - if (user is null) return NotFound(); return Ok(user); } } diff --git a/src/Core/API/API.Core/Dockerfile b/src/Core/API/API.Core/Dockerfile index 3c57ada..abfa65a 100644 --- a/src/Core/API/API.Core/Dockerfile +++ b/src/Core/API/API.Core/Dockerfile @@ -9,7 +9,8 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["API/API.Core/API.Core.csproj", "API/API.Core/"] -COPY ["Domain/Domain.csproj", "Domain/"] +COPY ["Domain.Entities/Domain.Entities.csproj", "Domain.Entities/"] +COPY ["Domain.Exceptions/Domain.Exceptions.csproj", "Domain.Exceptions/"] COPY ["Infrastructure/Infrastructure.Repository/Infrastructure.Repository.csproj", "Infrastructure/Infrastructure.Repository/"] COPY ["Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj", "Infrastructure/Infrastructure.Jwt/"] COPY ["Infrastructure/Infrastructure.PasswordHashing/Infrastructure.PasswordHashing.csproj", "Infrastructure/Infrastructure.PasswordHashing/"] diff --git a/src/Core/API/API.Core/GlobalException.cs b/src/Core/API/API.Core/GlobalException.cs new file mode 100644 index 0000000..9855e69 --- /dev/null +++ b/src/Core/API/API.Core/GlobalException.cs @@ -0,0 +1,84 @@ +// API.Core/Filters/GlobalExceptionFilter.cs + +using API.Core.Contracts.Common; +using Domain.Exceptions; +using FluentValidation; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace API.Core; + +public class GlobalExceptionFilter(ILogger logger) : IExceptionFilter +{ + public void OnException(ExceptionContext context) + { + logger.LogError(context.Exception, "Unhandled exception occurred"); + + switch (context.Exception) + { + case FluentValidation.ValidationException fluentValidationException: + var errors = fluentValidationException.Errors + .GroupBy(e => e.PropertyName) + .ToDictionary( + g => g.Key, + g => g.Select(e => e.ErrorMessage).ToArray() + ); + + context.Result = new BadRequestObjectResult(new + { + message = "Validation failed", + errors + }); + context.ExceptionHandled = true; + break; + + case ConflictException ex: + context.Result = new ObjectResult(new ResponseBody { Message = ex.Message }) + { + StatusCode = 409 + }; + context.ExceptionHandled = true; + break; + + case NotFoundException ex: + context.Result = new ObjectResult(new ResponseBody { Message = ex.Message }) + { + StatusCode = 404 + }; + context.ExceptionHandled = true; + break; + + case UnauthorizedException ex: + context.Result = new ObjectResult(new ResponseBody { Message = ex.Message }) + { + StatusCode = 401 + }; + context.ExceptionHandled = true; + break; + + case ForbiddenException ex: + context.Result = new ObjectResult(new ResponseBody { Message = ex.Message }) + { + StatusCode = 403 + }; + context.ExceptionHandled = true; + break; + + case Domain.Exceptions.ValidationException ex: + context.Result = new ObjectResult(new ResponseBody { Message = ex.Message }) + { + StatusCode = 400 + }; + context.ExceptionHandled = true; + break; + + default: + context.Result = new ObjectResult(new ResponseBody { Message = "An unexpected error occurred" }) + { + StatusCode = 500 + }; + context.ExceptionHandled = true; + break; + } + } +} diff --git a/src/Core/API/API.Core/Middleware/ValidationExceptionHandlingMiddleware.cs b/src/Core/API/API.Core/Middleware/ValidationExceptionHandlingMiddleware.cs deleted file mode 100644 index a631cd0..0000000 --- a/src/Core/API/API.Core/Middleware/ValidationExceptionHandlingMiddleware.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Net; -using System.Text.Json; -using API.Core.Contracts.Common; -using FluentValidation; - -namespace API.Core.Middleware; - -public class ValidationExceptionHandlingMiddleware(RequestDelegate next) -{ - public async Task InvokeAsync(HttpContext context) - { - try - { - await next(context); - } - catch (ValidationException ex) - { - await HandleValidationExceptionAsync(context, ex); - } - } - - private static Task HandleValidationExceptionAsync(HttpContext context, ValidationException exception) - { - context.Response.ContentType = "application/json"; - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - - var errors = exception.Errors - .Select(e => e.ErrorMessage) - .ToList(); - - var message = errors.Count == 1 - ? errors[0] - : "Validation failed. " + string.Join(" ", errors); - - var response = new ResponseBody - { - Message = message - }; - - var jsonOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - - return context.Response.WriteAsync(JsonSerializer.Serialize(response, jsonOptions)); - } -} diff --git a/src/Core/API/API.Core/Program.cs b/src/Core/API/API.Core/Program.cs index 82f1463..6c93d98 100644 --- a/src/Core/API/API.Core/Program.cs +++ b/src/Core/API/API.Core/Program.cs @@ -1,3 +1,5 @@ +using API.Core; +using Domain.Exceptions; using FluentValidation; using FluentValidation.AspNetCore; using Infrastructure.Jwt; @@ -6,34 +8,19 @@ using Infrastructure.Repository.Auth; using Infrastructure.Repository.Sql; using Infrastructure.Repository.UserAccount; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; using Service.Auth.Auth; using Service.UserManagement.User; - +using API.Core.Contracts.Common; var builder = WebApplication.CreateBuilder(args); -builder.Services.AddControllers() - .ConfigureApiBehaviorOptions(options => - { - options.InvalidModelStateResponseFactory = context => - { - var errors = context.ModelState.Values - .SelectMany(v => v.Errors) - .Select(e => e.ErrorMessage) - .ToList(); +// Global Exception Filter +builder.Services.AddControllers(options => +{ + options.Filters.Add(); +}); - var message = errors.Count == 1 - ? errors[0] - : string.Join(" ", errors); - - var response = new - { - message - }; - - return new BadRequestObjectResult(response); - }; - }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddOpenApi(); @@ -67,6 +54,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +// Register the exception filter +builder.Services.AddScoped(); var app = builder.Build(); @@ -90,6 +79,3 @@ lifetime.ApplicationStopping.Register(() => }); app.Run(); - -// Make Program class accessible to test projects -public partial class Program { } diff --git a/src/Core/API/API.Specs/Dockerfile b/src/Core/API/API.Specs/Dockerfile index 38a12d2..a22ad28 100644 --- a/src/Core/API/API.Specs/Dockerfile +++ b/src/Core/API/API.Specs/Dockerfile @@ -3,7 +3,8 @@ ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["API/API.Core/API.Core.csproj", "API/API.Core/"] COPY ["API/API.Specs/API.Specs.csproj", "API/API.Specs/"] -COPY ["Domain/Domain.csproj", "Domain/"] +COPY ["Domain.Entities/Domain.Entities.csproj", "Domain.Entities/"] +COPY ["Domain.Exceptions/Domain.Exceptions.csproj", "Domain.Exceptions/"] COPY ["Infrastructure/Infrastructure.Repository/Infrastructure.Repository.csproj", "Infrastructure/Infrastructure.Repository/"] COPY ["Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj", "Infrastructure/Infrastructure.Jwt/"] COPY ["Infrastructure/Infrastructure.PasswordHashing/Infrastructure.PasswordHashing.csproj", "Infrastructure/Infrastructure.PasswordHashing/"] diff --git a/src/Core/API/API.Specs/Features/Registration.feature b/src/Core/API/API.Specs/Features/Registration.feature index cf6d3d0..0ff53c3 100644 --- a/src/Core/API/API.Specs/Features/Registration.feature +++ b/src/Core/API/API.Specs/Features/Registration.feature @@ -12,25 +12,19 @@ Feature: User Registration And the response JSON should have "message" equal "User registered successfully." And the response JSON should have an access token - @Ignore Scenario: Registration fails with existing username Given the API is running - And I have an existing account with username "existinguser" When I submit a registration request with values: - | Username | FirstName | LastName | Email | DateOfBirth | Password | - | existinguser | Existing | User | existing@example.com | 1990-01-01 | Password1! | + | Username | FirstName | LastName | Email | DateOfBirth | Password | + | test.user | Test | User | example@example.com | 2001-11-11 | Password1! | Then the response has HTTP status 409 - And the response JSON should have "message" equal "Username already exists." - @Ignore Scenario: Registration fails with existing email Given the API is running - And I have an existing account with email "existing@example.com" When I submit a registration request with values: - | Username | FirstName | LastName | Email | DateOfBirth | Password | - | newuser | New | User | existing@example.com | 1990-01-01 | Password1! | + | Username | FirstName | LastName | Email | DateOfBirth | Password | + | newuser | New | User | test.user@thebiergarten.app | 1990-01-01 | Password1! | Then the response has HTTP status 409 - And the response JSON should have "message" equal "Email already in use." Scenario: Registration fails with missing required fields Given the API is running diff --git a/src/Core/API/API.Specs/Steps/AuthSteps.cs b/src/Core/API/API.Specs/Steps/AuthSteps.cs index b2e6fdb..adf145f 100644 --- a/src/Core/API/API.Specs/Steps/AuthSteps.cs +++ b/src/Core/API/API.Specs/Steps/AuthSteps.cs @@ -201,18 +201,6 @@ public class AuthSteps(ScenarioContext scenario) scenario[ResponseBodyKey] = responseBody; } - [Given("I have an existing account with username {string}")] - public void GivenIHaveAnExistingAccountWithUsername(string username) - { - - } - - [Given("I have an existing account with email {string}")] - public void GivenIHaveAnExistingAccountWithEmail(string email) - { - - } - [When("I submit a registration request using a GET request")] public async Task WhenISubmitARegistrationRequestUsingAGetRequest() { diff --git a/src/Core/Core.slnx b/src/Core/Core.slnx index c48bc6c..da5c14a 100644 --- a/src/Core/Core.slnx +++ b/src/Core/Core.slnx @@ -8,7 +8,8 @@ - + + diff --git a/src/Core/Database/Database.Seed/Database.Seed.csproj b/src/Core/Database/Database.Seed/Database.Seed.csproj index 9a44208..43384cb 100644 --- a/src/Core/Database/Database.Seed/Database.Seed.csproj +++ b/src/Core/Database/Database.Seed/Database.Seed.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Core/Database/Database.Seed/UserSeeder.cs b/src/Core/Database/Database.Seed/UserSeeder.cs index b7ae4ba..d47fe65 100644 --- a/src/Core/Database/Database.Seed/UserSeeder.cs +++ b/src/Core/Database/Database.Seed/UserSeeder.cs @@ -124,6 +124,7 @@ internal class UserSeeder : ISeeder int createdCredentials = 0; int createdVerifications = 0; + // create a known user for testing purposes { const string firstName = "Test"; const string lastName = "User"; @@ -264,4 +265,4 @@ internal class UserSeeder : ISeeder int offsetDays = random.Next(0, 365); return baseDate.AddDays(-offsetDays); } -} \ No newline at end of file +} diff --git a/src/Core/Domain/Domain.csproj b/src/Core/Domain.Entities/Domain.Entities.csproj similarity index 100% rename from src/Core/Domain/Domain.csproj rename to src/Core/Domain.Entities/Domain.Entities.csproj diff --git a/src/Core/Domain/Entities/UserAccount.cs b/src/Core/Domain.Entities/Entities/UserAccount.cs similarity index 100% rename from src/Core/Domain/Entities/UserAccount.cs rename to src/Core/Domain.Entities/Entities/UserAccount.cs diff --git a/src/Core/Domain/Entities/UserCredential.cs b/src/Core/Domain.Entities/Entities/UserCredential.cs similarity index 100% rename from src/Core/Domain/Entities/UserCredential.cs rename to src/Core/Domain.Entities/Entities/UserCredential.cs diff --git a/src/Core/Domain/Entities/UserVerification.cs b/src/Core/Domain.Entities/Entities/UserVerification.cs similarity index 100% rename from src/Core/Domain/Entities/UserVerification.cs rename to src/Core/Domain.Entities/Entities/UserVerification.cs diff --git a/src/Core/Domain.Exceptions/Domain.Exceptions.csproj b/src/Core/Domain.Exceptions/Domain.Exceptions.csproj new file mode 100644 index 0000000..237d661 --- /dev/null +++ b/src/Core/Domain.Exceptions/Domain.Exceptions.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/src/Core/Domain.Exceptions/Exceptions.cs b/src/Core/Domain.Exceptions/Exceptions.cs new file mode 100644 index 0000000..209880c --- /dev/null +++ b/src/Core/Domain.Exceptions/Exceptions.cs @@ -0,0 +1,33 @@ +namespace Domain.Exceptions; + +/// +/// Exception thrown when a resource conflict occurs (e.g., duplicate username, email already in use). +/// Maps to HTTP 409 Conflict. +/// +public class ConflictException(string message) : Exception(message); + +/// +/// Exception thrown when a requested resource is not found. +/// Maps to HTTP 404 Not Found. +/// +public class NotFoundException(string message) : Exception(message); + +// Domain.Exceptions/UnauthorizedException.cs + +/// +/// Exception thrown when authentication fails or is required. +/// Maps to HTTP 401 Unauthorized. +/// +public class UnauthorizedException(string message) : Exception(message); + +/// +/// Exception thrown when a user is authenticated but lacks permission to access a resource. +/// Maps to HTTP 403 Forbidden. +/// +public class ForbiddenException(string message) : Exception(message); + +/// +/// Exception thrown when business rule validation fails (distinct from FluentValidation). +/// Maps to HTTP 400 Bad Request. +/// +public class ValidationException(string message) : Exception(message); \ No newline at end of file diff --git a/src/Core/Infrastructure/Infrastructure.Repository.Tests/Dockerfile b/src/Core/Infrastructure/Infrastructure.Repository.Tests/Dockerfile index 5de992c..8f6eea8 100644 --- a/src/Core/Infrastructure/Infrastructure.Repository.Tests/Dockerfile +++ b/src/Core/Infrastructure/Infrastructure.Repository.Tests/Dockerfile @@ -1,7 +1,8 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["Domain/Domain.csproj", "Domain/"] +COPY ["Domain.Entities/Domain.Entities.csproj", "Domain.Entities/"] +COPY ["Domain.Exceptions/Domain.Exceptions.csproj", "Domain.Exceptions/"] COPY ["Infrastructure/Infrastructure.Repository/Infrastructure.Repository.csproj", "Infrastructure/Infrastructure.Repository/"] COPY ["Infrastructure/Infrastructure.Repository.Tests/Infrastructure.Repository.Tests.csproj", "Infrastructure/Infrastructure.Repository.Tests/"] RUN dotnet restore "Infrastructure/Infrastructure.Repository.Tests/Infrastructure.Repository.Tests.csproj" diff --git a/src/Core/Infrastructure/Infrastructure.Repository/Infrastructure.Repository.csproj b/src/Core/Infrastructure/Infrastructure.Repository/Infrastructure.Repository.csproj index 178bda8..c38e1f4 100644 --- a/src/Core/Infrastructure/Infrastructure.Repository/Infrastructure.Repository.csproj +++ b/src/Core/Infrastructure/Infrastructure.Repository/Infrastructure.Repository.csproj @@ -18,6 +18,6 @@ /> - + diff --git a/src/Core/Service/Service.Auth/Auth/ILoginService.cs b/src/Core/Service/Service.Auth/Auth/ILoginService.cs index 667a30a..9f9e022 100644 --- a/src/Core/Service/Service.Auth/Auth/ILoginService.cs +++ b/src/Core/Service/Service.Auth/Auth/ILoginService.cs @@ -5,5 +5,5 @@ namespace Service.Auth.Auth; public interface ILoginService { - Task LoginAsync(string username, string password); + Task LoginAsync(string username, string password); } diff --git a/src/Core/Service/Service.Auth/Auth/LoginService.cs b/src/Core/Service/Service.Auth/Auth/LoginService.cs index f931ce2..754a5a4 100644 --- a/src/Core/Service/Service.Auth/Auth/LoginService.cs +++ b/src/Core/Service/Service.Auth/Auth/LoginService.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Domain.Entities; +using Domain.Exceptions; using Infrastructure.PasswordHashing; using Infrastructure.Repository.Auth; @@ -11,18 +12,24 @@ public class LoginService( ) : ILoginService { - public async Task LoginAsync(string username, string password) + public async Task LoginAsync(string username, string password) { // Attempt lookup by username var user = await authRepo.GetUserByUsernameAsync(username); // the user was not found - if (user is null) return null; + if (user is null) + throw new UnauthorizedException("Invalid username or password."); // @todo handle expired passwords var activeCred = await authRepo.GetActiveCredentialByUserAccountIdAsync(user.UserAccountId); - if (activeCred is null) return null; - return !passwordInfrastructure.Verify(password, activeCred.Hash) ? null : user; + if (activeCred is null) + throw new UnauthorizedException("Invalid username or password."); + + if (!passwordInfrastructure.Verify(password, activeCred.Hash)) + throw new UnauthorizedException("Invalid username or password."); + + return user; } } diff --git a/src/Core/Service/Service.Auth/Auth/RegisterService.cs b/src/Core/Service/Service.Auth/Auth/RegisterService.cs index 5b1493b..c136c16 100644 --- a/src/Core/Service/Service.Auth/Auth/RegisterService.cs +++ b/src/Core/Service/Service.Auth/Auth/RegisterService.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Domain.Entities; +using Domain.Exceptions; using Infrastructure.PasswordHashing; using Infrastructure.Repository.Auth; @@ -13,12 +14,16 @@ public class RegisterService( public async Task RegisterAsync(UserAccount userAccount, string password) { // Check if user already exists - var user = await authRepo.GetUserByUsernameAsync(userAccount.Username); - if (user is not null) + var existingUsername = await authRepo.GetUserByUsernameAsync(userAccount.Username); + var existingEmail = await authRepo.GetUserByEmailAsync(userAccount.Email); + + if (existingUsername != null || existingEmail != null) { - return null!; + throw new ConflictException("Username or email already exists"); } + + // password hashing var hashed = passwordInfrastructure.Hash(password); @@ -32,5 +37,5 @@ public class RegisterService( hashed); } - + } diff --git a/src/Core/Service/Service.Auth/Service.Auth.csproj b/src/Core/Service/Service.Auth/Service.Auth.csproj index 48566dc..6d9133a 100644 --- a/src/Core/Service/Service.Auth/Service.Auth.csproj +++ b/src/Core/Service/Service.Auth/Service.Auth.csproj @@ -10,8 +10,10 @@ - - + + + diff --git a/src/Core/Service/Service.UserManagement/Service.UserManagement.csproj b/src/Core/Service/Service.UserManagement/Service.UserManagement.csproj index 776d954..b26eb31 100644 --- a/src/Core/Service/Service.UserManagement/Service.UserManagement.csproj +++ b/src/Core/Service/Service.UserManagement/Service.UserManagement.csproj @@ -7,7 +7,9 @@ - + + diff --git a/src/Core/Service/Service.UserManagement/User/IUserService.cs b/src/Core/Service/Service.UserManagement/User/IUserService.cs index 71dcfac..ab66525 100644 --- a/src/Core/Service/Service.UserManagement/User/IUserService.cs +++ b/src/Core/Service/Service.UserManagement/User/IUserService.cs @@ -5,7 +5,7 @@ namespace Service.UserManagement.User; public interface IUserService { Task> GetAllAsync(int? limit = null, int? offset = null); - Task GetByIdAsync(Guid id); + Task GetByIdAsync(Guid id); Task UpdateAsync(UserAccount userAccount); } diff --git a/src/Core/Service/Service.UserManagement/User/UserService.cs b/src/Core/Service/Service.UserManagement/User/UserService.cs index 7391ec0..43a7816 100644 --- a/src/Core/Service/Service.UserManagement/User/UserService.cs +++ b/src/Core/Service/Service.UserManagement/User/UserService.cs @@ -1,4 +1,5 @@ using Domain.Entities; +using Domain.Exceptions; using Infrastructure.Repository.UserAccount; namespace Service.UserManagement.User; @@ -10,9 +11,12 @@ public class UserService(IUserAccountRepository repository) : IUserService return await repository.GetAllAsync(limit, offset); } - public async Task GetByIdAsync(Guid id) + public async Task GetByIdAsync(Guid id) { - return await repository.GetByIdAsync(id); + var user = await repository.GetByIdAsync(id); + if (user is null) + throw new NotFoundException($"User with ID {id} not found"); + return user; } public async Task UpdateAsync(UserAccount userAccount)