Repository: Danny-Developer2/Backend-Portafolio
Pull request #1: Update
Author: login: Danny-Developer2
Created: 2025-03-03T20:26:36Z | Merged: 2025-03-03T20:27:19Z

Ok

## Summary by Sourcery

Improves user management by implementing an interface for the user repository, refactoring the UsersController to use the repository interface, and improving error handling.

Enhancements:
- Implement IUserRepository interface and UserRepository class to manage user data access.
- Refactor UsersController to use IUserRepository for data access.
- Improve error handling in UsersController, returning appropriate HTTP status codes and messages instead of throwing exceptions.
- Update user management endpoints to use more descriptive names (e.g., 'by-id').


Diff:
diff --git a/Backend/API/Controllers/SkillsControllers.cs b/Backend/API/Controllers/SkillsControllers.cs
index 071daa3c..04bf1aa1 100644
--- a/Backend/API/Controllers/SkillsControllers.cs
+++ b/Backend/API/Controllers/SkillsControllers.cs
@@ -14,6 +14,7 @@ namespace API.Controllers
     public class SkillsController : ControllerBase
     {
         private readonly SkillRepository _skillRepository;
+        
 
         public SkillsController(SkillRepository skillRepository)
         {
diff --git a/Backend/API/Controllers/UsersControllers.cs b/Backend/API/Controllers/UsersControllers.cs
index 6eb1f088..8ccac781 100644
--- a/Backend/API/Controllers/UsersControllers.cs
+++ b/Backend/API/Controllers/UsersControllers.cs
@@ -7,47 +7,40 @@
 using API.Errors;
 using API.DTOs;
 using Microsoft.EntityFrameworkCore;
-using SQLitePCL;  // Asegúrate de incluir el espacio de nombres para ApiException
+using SQLitePCL;
+using API.Interfaces;  // Asegúrate de incluir el espacio de nombres para ApiException
 
 namespace API.Controllers
 {
     [ApiController]
     [Route("api/[controller]")]
+   
+
     public class UsersController : ControllerBase
     {
-        private readonly UserRepository _userRepository;
-
-         private readonly DataContext _context;
+        private readonly IUserRepository _userRepository;
 
-        public UsersController(UserRepository userRepository, DataContext context)
+        public UsersController(IUserRepository userRepository)
         {
             _userRepository = userRepository;
-
-            _context = context;
         }
 
-        // Obtener todos los usuarios
-       
         // Obtener usuario por ID
-        [HttpGet("{id:int}")]
+        [HttpGet("by-id")]
         public async Task<ActionResult<AppUser>> GetUserById(int id)
         {
             try
             {
                 var user = await _userRepository.GetUserById(id);
-                if (user == null)
-                {
-                    throw new ApiException(404, "Usuario no encontrado"); // Lanza ApiException si el usuario no existe
-                }
                 return Ok(user);
             }
             catch (KeyNotFoundException e)
             {
-                throw new ApiException(404, e.Message); // Lanza ApiException si el usuario no se encuentra
+                return NotFound(new { message = e.Message });
             }
             catch (Exception ex)
             {
-                throw new ApiException(500, $"Error al obtener el usuario: {ex.Message}"); // Lanza ApiException en caso de error inesperado
+                return StatusCode(500, new { message = $"Error al obtener el usuario: {ex.Message}" });
             }
         }
 
@@ -55,85 +48,32 @@ public async Task<ActionResult<AppUser>> GetUserById(int id)
         [HttpPost]
         public async Task<ActionResult<AppUser>> CreateUser(UserDTO userDTO)
         {
-            try
-            {
-                if (userDTO == null)
-                {
-                    throw new ApiException(400, "Los datos del usuario son inválidos."); // Lanza ApiException si los datos son inválidos
-                }
+            if (userDTO is null) return BadRequest("Los datos del usuario son inválidos.");
 
-                var user = await _userRepository.AddUser(userDTO);
-                return CreatedAtAction(nameof(GetUserById), new { id = user.Id }, user);
-            }
-            catch (InvalidOperationException e)
-            {
-                throw new ApiException(400, e.Message); // Lanza ApiException si la operación es inválida
-            }
-            catch (Exception ex)
-            {
-                throw new ApiException(500, $"Error al crear el usuario: {ex.Message}"); // Lanza ApiException en caso de error inesperado
-            }
+            var user = await _userRepository.AddUser(userDTO);
+            return CreatedAtAction(nameof(GetUserById), new { id = user.Id }, user);
         }
 
         // Actualizar usuario
-        [HttpPut("{id:int}")]
+        [HttpPut("by-id")]
         public async Task<IActionResult> UpdateUser(int id, UserDTO userDTO)
         {
-            if (id != userDTO.Id)
-            {
-                throw new ApiException(400, "Los IDs no coinciden"); // Lanza ApiException si los IDs no coinciden
-            }
+            if (id != userDTO.Id) return BadRequest("Los IDs no coinciden");
 
-            try
-            {
-                var updatedUser = await _userRepository.UpdateUser(id, userDTO);
-                if (updatedUser == null)
-                {
-                    throw new ApiException(404, "Usuario no encontrado"); // Lanza ApiException si no se encuentra el usuario
-                }
-                return Ok(updatedUser);
-            }
-            catch (KeyNotFoundException e)
-            {
-                throw new ApiException(404, e.Message); // Lanza ApiException si el usuario no se encuentra
-            }
-            catch (InvalidOperationException e)
-            {
-                throw new ApiException(400, e.Message); // Lanza ApiException si la operación es inválida
-            }
-            catch (Exception ex)
-            {
-                throw new ApiException(500, $"Error al actualizar el usuario: {ex.Message}"); // Lanza ApiException en caso de error inesperado
-            }
+            var updatedUser = await _userRepository.UpdateUser(id, userDTO);
+            return updatedUser is null ? NotFound("Usuario no encontrado") : Ok(updatedUser);
         }
 
         // Eliminar usuario
-        [HttpDelete("{id:int}")]
+        [HttpDelete("by-id")]
         public async Task<IActionResult> DeleteUser(int id)
         {
-            try
-            {
-                var user = await _userRepository.GetUserById(id);
-                if (user == null)
-                {
-                    throw new ApiException(404, "Usuario no encontrado"); // Lanza ApiException si no se encuentra el usuario
-                }
-
-                await _userRepository.DeleteUser(id);
-                return NoContent(); // 204 No Content
-            }
-            catch (KeyNotFoundException e)
-            {
-                throw new ApiException(404, e.Message); // Lanza ApiException si el usuario no se encuentra
-            }
-            catch (ApiException ex)
-            {
-                return StatusCode(ex.StatusCode, ex.Message); // Manejo de errores controlados
-            }
-            catch (Exception ex)
+            var result = await _userRepository.DeleteUser(id);
+            if (!result)
             {
-                throw new ApiException(500, $"Error al eliminar el usuario: {ex.Message}"); // Lanza ApiException en caso de error inesperado
+                return NotFound(new { message = "Usuario no encontrado" });
             }
+            return NoContent(); // ✅ Código 204 si se eliminó correctamente
         }
     }
 }
diff --git a/Backend/API/Program.cs b/Backend/API/Program.cs
index 04aad138..72a7eb71 100644
--- a/Backend/API/Program.cs
+++ b/Backend/API/Program.cs
@@ -7,6 +7,7 @@
 using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.IdentityModel.Tokens;
 using System.Text;
+using API.Interfaces;
 
 
 var builder = WebApplication.CreateBuilder(args);
@@ -39,6 +40,9 @@
 {
     options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin","User"));
 });
+
+
+
 // Configurar la conexión a la base de datos
 
 builder.Services.AddDbContext<DataContext>(options =>
@@ -72,6 +76,8 @@
 
 builder.Services.AddScoped<UserRepository, UserRepository>();
 
+builder.Services.AddScoped<IUserRepository, UserRepository>();
+
 
 
 
diff --git a/Backend/API/Repositories/Interfaces/IUserRepository.cs b/Backend/API/Repositories/Interfaces/IUserRepository.cs
new file mode 100644
index 00000000..d0d23f19
--- /dev/null
+++ b/Backend/API/Repositories/Interfaces/IUserRepository.cs
@@ -0,0 +1,16 @@
+using API.DTO;
+using API.Entities;
+using System.Threading.Tasks;
+
+namespace API.Interfaces
+{
+    public interface IUserRepository
+    {
+
+        Task<List<AppUser>> GetUsers();
+        Task<AppUser> GetUserById(int id);
+        Task<AppUser> AddUser(UserDTO userDTO);
+        Task<AppUser> UpdateUser(int id, UserDTO userDTO);
+        Task<bool> DeleteUser(int id);
+    }
+}
diff --git a/Backend/API/Repositories/UserRespository.cs b/Backend/API/Repositories/UserRespository.cs
index 5ec97e91..f0056d78 100644
--- a/Backend/API/Repositories/UserRespository.cs
+++ b/Backend/API/Repositories/UserRespository.cs
@@ -4,12 +4,12 @@
 using System.Threading.Tasks;
 using Microsoft.EntityFrameworkCore;
 using API.Entities;
-using API.Data;
 using API.DTO;
+using API.Interfaces;
 
 namespace API.Data
 {
-    public class UserRepository
+    public class UserRepository : IUserRepository
     {
         private readonly DataContext _context;
 
@@ -38,7 +38,11 @@ public async Task<AppUser> GetUserById(int id)
         // Crear un nuevo usuario
         public async Task<AppUser> AddUser(UserDTO userDTO)
         {
-            // Verificar si el correo ya existe
+            if (string.IsNullOrWhiteSpace(userDTO.PasswordHash))
+            {
+                throw new InvalidOperationException("La contraseña es obligatoria.");
+            }
+
             if (await _context.Users.AnyAsync(u => u.Email == userDTO.Email))
             {
                 throw new InvalidOperationException("El correo electrónico ya está registrado.");
@@ -88,18 +92,17 @@ public async Task<AppUser> UpdateUser(int id, UserDTO userDTO)
         }
 
         // Eliminar usuario
-        public async Task DeleteUser(int id)
+        public async Task<bool> DeleteUser(int id)
         {
             var user = await _context.Users.FindAsync(id);
-            if (user != null)
-            {
-                _context.Users.Remove(user);
-                await _context.SaveChangesAsync();
-            }
-            else
+            if (user == null)
             {
-                throw new KeyNotFoundException("Usuario no encontrado.");
+                return false; // ✅ Mejor que lanzar una excepción
             }
+
+            _context.Users.Remove(user);
+            await _context.SaveChangesAsync();
+            return true;
         }
     }
 }
diff --git a/Backend/API/bin/Debug/net9.0/API.dll b/Backend/API/bin/Debug/net9.0/API.dll
index afb2319c..7c1c9b4c 100644
Binary files a/Backend/API/bin/Debug/net9.0/API.dll and b/Backend/API/bin/Debug/net9.0/API.dll differ
diff --git a/Backend/API/bin/Debug/net9.0/API.pdb b/Backend/API/bin/Debug/net9.0/API.pdb
index 927c95e1..9a7c060d 100644
Binary files a/Backend/API/bin/Debug/net9.0/API.pdb and b/Backend/API/bin/Debug/net9.0/API.pdb differ
diff --git a/Backend/API/obj/Debug/net9.0/API.AssemblyInfo.cs b/Backend/API/obj/Debug/net9.0/API.AssemblyInfo.cs
index 15c17777..eac6aa31 100644
--- a/Backend/API/obj/Debug/net9.0/API.AssemblyInfo.cs
+++ b/Backend/API/obj/Debug/net9.0/API.AssemblyInfo.cs
@@ -13,10 +13,10 @@
 [assembly: System.Reflection.AssemblyCompanyAttribute("API")]
 [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
 [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
-[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+d2047a8739f0145a2ae9d203595ca2065370185b")]
 [assembly: System.Reflection.AssemblyProductAttribute("API")]
 [assembly: System.Reflection.AssemblyTitleAttribute("API")]
 [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
 
-// Generated by the MSBuild WriteCodeFragment class.
+// Generado por la clase WriteCodeFragment de MSBuild.
 
diff --git a/Backend/API/obj/Debug/net9.0/API.AssemblyInfoInputs.cache b/Backend/API/obj/Debug/net9.0/API.AssemblyInfoInputs.cache
index 6edf5ac7..46ffe127 100644
--- a/Backend/API/obj/Debug/net9.0/API.AssemblyInfoInputs.cache
+++ b/Backend/API/obj/Debug/net9.0/API.AssemblyInfoInputs.cache
@@ -1 +1 @@
-7cf0cf1bc0c1bb1fa8ec578557b749a7cc57ef8cae429498fee10b33ff11b247
+94d6f24c83cc5e423c024b459fc4a7abcbc349255ce73c37fbf89be2ce0e33aa
diff --git a/Backend/API/obj/Debug/net9.0/API.csproj.CoreCompileInputs.cache b/Backend/API/obj/Debug/net9.0/API.csproj.CoreCompileInputs.cache
index 942dce33..52e842a3 100644
--- a/Backend/API/obj/Debug/net9.0/API.csproj.CoreCompileInputs.cache
+++ b/Backend/API/obj/Debug/net9.0/API.csproj.CoreCompileInputs.cache
@@ -1 +1 @@
-633f9bfc1f678f250ef04411bde0a25ae770fa677e424bafc000ca6226cf86db
+bd0af432cb68c7f08ab5fd7e0157aac68eaedc636e60c4cfc23eaf21a36a206c
diff --git a/Backend/API/obj/Debug/net9.0/API.csproj.FileListAbsolute.txt b/Backend/API/obj/Debug/net9.0/API.csproj.FileListAbsolute.txt
index 29111d05..40b04662 100644
--- a/Backend/API/obj/Debug/net9.0/API.csproj.FileListAbsolute.txt
+++ b/Backend/API/obj/Debug/net9.0/API.csproj.FileListAbsolute.txt
@@ -155,3 +155,4 @@
 /Users/juancazas/Desktop/Curso .Net/Backend/API/bin/Debug/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll
 /Users/juancazas/Desktop/Curso .Net/Backend/API/bin/Debug/net9.0/Microsoft.IdentityModel.Tokens.dll
 /Users/juancazas/Desktop/Curso .Net/Backend/API/bin/Debug/net9.0/System.IdentityModel.Tokens.Jwt.dll
+/Users/juancazas/Desktop/Curso .Net/Backend/API/obj/Debug/net9.0/API.sourcelink.json
diff --git a/Backend/API/obj/Debug/net9.0/API.dll b/Backend/API/obj/Debug/net9.0/API.dll
index afb2319c..7c1c9b4c 100644
Binary files a/Backend/API/obj/Debug/net9.0/API.dll and b/Backend/API/obj/Debug/net9.0/API.dll differ
diff --git a/Backend/API/obj/Debug/net9.0/API.pdb b/Backend/API/obj/Debug/net9.0/API.pdb
index 927c95e1..9a7c060d 100644
Binary files a/Backend/API/obj/Debug/net9.0/API.pdb and b/Backend/API/obj/Debug/net9.0/API.pdb differ
diff --git a/Backend/API/obj/Debug/net9.0/API.sourcelink.json b/Backend/API/obj/Debug/net9.0/API.sourcelink.json
new file mode 100644
index 00000000..c0899620
--- /dev/null
+++ b/Backend/API/obj/Debug/net9.0/API.sourcelink.json
@@ -0,0 +1 @@
+{"documents":{"/Users/juancazas/Desktop/Curso .Net/*":"https://raw.githubusercontent.com/Danny-Developer2/Backend-Portafolio/d2047a8739f0145a2ae9d203595ca2065370185b/*"}}
\ No newline at end of file
diff --git a/Backend/API/obj/Debug/net9.0/ref/API.dll b/Backend/API/obj/Debug/net9.0/ref/API.dll
index c12e2830..6cd2393e 100644
Binary files a/Backend/API/obj/Debug/net9.0/ref/API.dll and b/Backend/API/obj/Debug/net9.0/ref/API.dll differ
diff --git a/Backend/API/obj/Debug/net9.0/refint/API.dll b/Backend/API/obj/Debug/net9.0/refint/API.dll
index c12e2830..6cd2393e 100644
Binary files a/Backend/API/obj/Debug/net9.0/refint/API.dll and b/Backend/API/obj/Debug/net9.0/refint/API.dll differ


Reviews:
user: login: sourcery-ai[bot]
body: Hey @Danny-Developer2 - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- Consider adding a base controller to handle exception and error responses in a consistent manner.
- Good job implementing the repository pattern with an interface.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 2 issues found
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Complexity**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T20:27:23Z

Review comments:
diff_hunk: @@ -38,7 +38,11 @@ public async Task<AppUser> GetUserById(int id)
         // Crear un nuevo usuario
         public async Task<AppUser> AddUser(UserDTO userDTO)
         {
-            // Verificar si el correo ya existe
+            if (string.IsNullOrWhiteSpace(userDTO.PasswordHash))
+            {
+                throw new InvalidOperationException("La contraseña es obligatoria.");
path: Backend/API/Repositories/UserRespository.cs
user: login: sourcery-ai[bot]
body: **question (bug_risk):** Validate that 'PasswordHash' is the correct field for password verification.

If the intent is to require a raw password value, consider verifying the correct property name or whether additional processing (like hashing) is required before validation.
created_at: 2025-03-03T20:27:23Z
author_association: NONE
diff_hunk: @@ -72,6 +76,8 @@
 
 builder.Services.AddScoped<UserRepository, UserRepository>();
 
+builder.Services.AddScoped<IUserRepository, UserRepository>();
path: Backend/API/Program.cs
user: login: sourcery-ai[bot]
body: **suggestion:** Consider reviewing duplicate DI registrations for UserRepository.

The code still registers UserRepository with its concrete type in addition to the interface registration. If the interface is preferred, removing the duplicate registration might be beneficial to avoid potential ambiguity.

Suggested implementation:

```csharp

```

You should ensure that the IUserRepository interface is correctly injected in all dependent classes if necessary.
created_at: 2025-03-03T20:27:23Z
author_association: NONE

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-03-03T20:26:40Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request refactors the UsersController and UserRepository to improve error handling, code readability, and maintainability. It removes the custom ApiException, implements standard IActionResult error responses, and modifies the DeleteUser method to return a boolean. Additionally, an interface was created for the UserRepository.

#### Sequence diagram for DeleteUser

```mermaid
sequenceDiagram
    participant Controller as UsersController
    participant Repository as UserRepository
    participant DB as DataContext

    Controller->>Repository: DeleteUser(id)
    Repository->>DB: Users.FindAsync(id)
    DB-->>Repository: AppUser or null
    alt User found
        Repository->>DB: Users.Remove(user)
        Repository->>DB: SaveChangesAsync()
        DB-->>Repository: int
        Repository-->>Controller: NoContent()
    else User not found
        Repository-->>Controller: NotFound(message)
    end
```

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Refactor UsersController to remove ApiException usage and improve error handling. | <ul><li>Removed custom ApiException usage.</li><li>Implemented standard IActionResult error responses (BadRequest, NotFound, StatusCode).</li><li>Simplified conditional checks for null values and ID mismatches.</li><li>Improved readability by removing redundant try-catch blocks.</li></ul> | `Backend/API/Controllers/UsersControllers.cs` |
| Update UserRepository to return boolean for delete operation and add password validation. | <ul><li>Modified DeleteUser to return a boolean indicating success or failure.</li><li>Added password validation to AddUser method.</li><li>Removed unnecessary exception throwing.</li><li>Improved code readability and maintainability.</li></ul> | `Backend/API/Repositories/UserRespository.cs` |
| Added IUserRepository interface and registered it in Program.cs. | <ul><li>Created IUserRepository interface.</li><li>Registered IUserRepository with UserRepository in Program.cs.</li><li>Updated UsersController to use IUserRepository.</li><li>Promotes loose coupling and testability.</li></ul> | `Backend/API/Program.cs`<br/>`Backend/API/Repositories/Interfaces/IUserRepository.cs` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: NONE
user: login: Danny-Developer2
created_at: 2025-03-03T20:27:24Z
body: ok
author_association: OWNER