Переглянути джерело

Merge branch 'main' into Add-System-Drives-Commands

Tim Jones 2 місяців тому
батько
коміт
c48335952e
100 змінених файлів з 589 додано та 1104 видалено
  1. BIN
      .DS_Store
  2. 2 0
      .gitignore
  3. 168 848
      Commands.md
  4. 15 0
      RackPeek.Domain/Helpers/Normalize.cs
  5. 6 5
      RackPeek.Domain/Resources/Hardware/AccessPoints/AddAccessPointUseCase.cs
  6. 2 2
      RackPeek.Domain/Resources/Hardware/AccessPoints/DeleteAccessPointUseCase.cs
  7. 3 3
      RackPeek.Domain/Resources/Hardware/AccessPoints/DescribeAccessPointUseCase.cs
  8. 8 3
      RackPeek.Domain/Resources/Hardware/AccessPoints/GetAccessPointUseCase.cs
  9. 1 1
      RackPeek.Domain/Resources/Hardware/AccessPoints/UpdateAccessPointUseCase.cs
  10. 5 4
      RackPeek.Domain/Resources/Hardware/Desktops/AddDesktopUseCase.cs
  11. 7 3
      RackPeek.Domain/Resources/Hardware/Desktops/Cpus/AddDesktopCpuUseCase.cs
  12. 8 4
      RackPeek.Domain/Resources/Hardware/Desktops/Cpus/RemoveDesktopCpuUseCase.cs
  13. 8 4
      RackPeek.Domain/Resources/Hardware/Desktops/Cpus/UpdateDesktopCpuUseCase.cs
  14. 2 1
      RackPeek.Domain/Resources/Hardware/Desktops/DeleteDesktopUseCase.cs
  15. 3 2
      RackPeek.Domain/Resources/Hardware/Desktops/DescribeDesktopUseCase.cs
  16. 7 3
      RackPeek.Domain/Resources/Hardware/Desktops/Drives/AddDesktopDriveUseCase.cs
  17. 8 4
      RackPeek.Domain/Resources/Hardware/Desktops/Drives/RemoveDesktopDriveUseCase.cs
  18. 8 4
      RackPeek.Domain/Resources/Hardware/Desktops/Drives/UpdateDesktopDriveUseCase.cs
  19. 8 2
      RackPeek.Domain/Resources/Hardware/Desktops/GetDesktopUseCase.cs
  20. 7 3
      RackPeek.Domain/Resources/Hardware/Desktops/Gpus/AddDesktopGpuUseCase.cs
  21. 8 4
      RackPeek.Domain/Resources/Hardware/Desktops/Gpus/RemoveDesktopGpuUseCase.cs
  22. 8 4
      RackPeek.Domain/Resources/Hardware/Desktops/Gpus/UpdateDesktopGpuUseCase.cs
  23. 7 3
      RackPeek.Domain/Resources/Hardware/Desktops/Nics/AddDesktopNicUseCase.cs
  24. 8 4
      RackPeek.Domain/Resources/Hardware/Desktops/Nics/RemoveDesktopNicUseCase.cs
  25. 8 4
      RackPeek.Domain/Resources/Hardware/Desktops/Nics/UpdateDesktopNicUseCase.cs
  26. 1 0
      RackPeek.Domain/Resources/Hardware/Desktops/UpdateDesktopUseCase.cs
  27. 5 5
      RackPeek.Domain/Resources/Hardware/Firewalls/AddFirewallUseCase.cs
  28. 2 1
      RackPeek.Domain/Resources/Hardware/Firewalls/DeleteFirewallUseCase.cs
  29. 4 3
      RackPeek.Domain/Resources/Hardware/Firewalls/DescribeFirewallUseCase.cs
  30. 8 2
      RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallUseCase.cs
  31. 1 0
      RackPeek.Domain/Resources/Hardware/Firewalls/UpdateFirewallUseCase.cs
  32. 3 2
      RackPeek.Domain/Resources/Hardware/GetHardwareSystemTreeUseCase.cs
  33. 5 4
      RackPeek.Domain/Resources/Hardware/Laptops/AddLaptopUseCase.cs
  34. 1 0
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/AddDesktopCpuUseCase.cs
  35. 1 0
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/RemoveDesktopCpuUseCase.cs
  36. 3 2
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/UpdateDesktopCpuUseCase.cs
  37. 2 1
      RackPeek.Domain/Resources/Hardware/Laptops/DeleteLaptopUseCase.cs
  38. 3 2
      RackPeek.Domain/Resources/Hardware/Laptops/DescribeLaptopUseCase.cs
  39. 2 1
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/AddDesktopDriveUseCase.cs
  40. 3 2
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/RemoveDesktopDriveUseCase.cs
  41. 3 2
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/UpdateDesktopDriveUseCase.cs
  42. 8 2
      RackPeek.Domain/Resources/Hardware/Laptops/GetDesktopUseCase.cs
  43. 1 0
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/AddDesktopGpuUseCase.cs
  44. 3 2
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/RemoveDesktopGpuUseCase.cs
  45. 3 2
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/UpdateDesktopGpuUseCase.cs
  46. 5 5
      RackPeek.Domain/Resources/Hardware/Routers/AddRouterUseCase.cs
  47. 2 1
      RackPeek.Domain/Resources/Hardware/Routers/DeleteRouterUseCase.cs
  48. 3 2
      RackPeek.Domain/Resources/Hardware/Routers/DescribeRouterUseCase.cs
  49. 8 2
      RackPeek.Domain/Resources/Hardware/Routers/GetRouterUseCase.cs
  50. 1 0
      RackPeek.Domain/Resources/Hardware/Routers/UpdateRouterUseCase.cs
  51. 5 5
      RackPeek.Domain/Resources/Hardware/Servers/AddServerUseCase.cs
  52. 4 3
      RackPeek.Domain/Resources/Hardware/Servers/Cpus/AddCpuUseCase.cs
  53. 4 3
      RackPeek.Domain/Resources/Hardware/Servers/Cpus/RemoveCpuUseCase.cs
  54. 4 3
      RackPeek.Domain/Resources/Hardware/Servers/Cpus/UpdateCpuUseCase.cs
  55. 2 1
      RackPeek.Domain/Resources/Hardware/Servers/DeleteServerUseCase.cs
  56. 3 2
      RackPeek.Domain/Resources/Hardware/Servers/DescribeServerUseCase.cs
  57. 4 3
      RackPeek.Domain/Resources/Hardware/Servers/Drives/AddDriveUseCase.cs
  58. 4 3
      RackPeek.Domain/Resources/Hardware/Servers/Drives/RemoveDriveUseCase.cs
  59. 4 3
      RackPeek.Domain/Resources/Hardware/Servers/Drives/UpdateDriveUseCase.cs
  60. 8 2
      RackPeek.Domain/Resources/Hardware/Servers/GetServerUseCase.cs
  61. 5 4
      RackPeek.Domain/Resources/Hardware/Servers/Gpus/AddGpuUseCase.cs
  62. 4 3
      RackPeek.Domain/Resources/Hardware/Servers/Gpus/RemoveGpuUseCase.cs
  63. 4 3
      RackPeek.Domain/Resources/Hardware/Servers/Gpus/UpdateGpuUseCase.cs
  64. 5 4
      RackPeek.Domain/Resources/Hardware/Servers/Nics/AddNicUseCase.cs
  65. 5 4
      RackPeek.Domain/Resources/Hardware/Servers/Nics/RemoveNicUseCase.cs
  66. 5 4
      RackPeek.Domain/Resources/Hardware/Servers/Nics/UpdateNicUseCase.cs
  67. 1 0
      RackPeek.Domain/Resources/Hardware/Servers/UpdateServerUseCase.cs
  68. 5 5
      RackPeek.Domain/Resources/Hardware/Switches/AddSwitchUseCase.cs
  69. 2 1
      RackPeek.Domain/Resources/Hardware/Switches/DeleteSwitchUseCase.cs
  70. 4 3
      RackPeek.Domain/Resources/Hardware/Switches/DescribeSwitchUseCase.cs
  71. 8 2
      RackPeek.Domain/Resources/Hardware/Switches/GetSwitchUseCase.cs
  72. 1 0
      RackPeek.Domain/Resources/Hardware/Switches/UpdateSwitchUseCase.cs
  73. 5 4
      RackPeek.Domain/Resources/Hardware/UpsUnits/AddUpsUseCase.cs
  74. 2 1
      RackPeek.Domain/Resources/Hardware/UpsUnits/DeleteUpsUseCase.cs
  75. 3 2
      RackPeek.Domain/Resources/Hardware/UpsUnits/DescribeUpsUseCase.cs
  76. 7 2
      RackPeek.Domain/Resources/Hardware/UpsUnits/GetUpsUnitUseCase.cs
  77. 1 0
      RackPeek.Domain/Resources/Hardware/UpsUnits/UpdateUpsUseCase.cs
  78. 8 0
      RackPeek.Domain/Resources/IResourceRepository.cs
  79. 6 5
      RackPeek.Domain/Resources/Services/UseCases/AddServiceUseCase.cs
  80. 1 0
      RackPeek.Domain/Resources/Services/UseCases/DeleteServiceUseCase.cs
  81. 3 2
      RackPeek.Domain/Resources/Services/UseCases/DescribeServiceUseCase.cs
  82. 10 2
      RackPeek.Domain/Resources/Services/UseCases/GetServiceUseCase.cs
  83. 9 2
      RackPeek.Domain/Resources/Services/UseCases/UpdateServiceUseCase.cs
  84. 6 5
      RackPeek.Domain/Resources/SystemResources/UseCases/AddSystemUseCase.cs
  85. 4 3
      RackPeek.Domain/Resources/SystemResources/UseCases/DeleteSystemUseCase.cs
  86. 3 2
      RackPeek.Domain/Resources/SystemResources/UseCases/DescribeSystemUseCase.cs
  87. 8 5
      RackPeek.Domain/Resources/SystemResources/UseCases/GetSystemServiceTreeUseCase.cs
  88. 10 2
      RackPeek.Domain/Resources/SystemResources/UseCases/GetSystemUseCase.cs
  89. 9 1
      RackPeek.Domain/Resources/SystemResources/UseCases/UpdateSystemUseCase.cs
  90. BIN
      RackPeek.Web/config/.DS_Store
  91. 3 1
      RackPeek/CliBootstrap.cs
  92. 1 7
      RackPeek/Commands/AccessPoints/AccessPointDescribeCommand.cs
  93. 0 6
      RackPeek/Commands/AccessPoints/AccessPointGetByNameCommand.cs
  94. 1 7
      RackPeek/Commands/Desktops/DesktopDescribeCommand.cs
  95. 0 6
      RackPeek/Commands/Desktops/DesktopGetByNameCommand.cs
  96. 0 6
      RackPeek/Commands/Desktops/DesktopTreeCommand.cs
  97. 0 6
      RackPeek/Commands/Firewalls/FirewallDescribeCommand.cs
  98. 0 6
      RackPeek/Commands/Firewalls/FirewallGetByNameCommand.cs
  99. 1 1
      RackPeek/Commands/Firewalls/FirewallGetCommand.cs
  100. 1 1
      RackPeek/Commands/Firewalls/FirewallSetCommand.cs

+ 2 - 0
.gitignore

@@ -416,3 +416,5 @@ FodyWeavers.xsd
 *.msix
 *.msm
 *.msp
+
+**/config/

Різницю між файлами не показано, бо вона завелика
+ 168 - 848
Commands.md


+ 15 - 0
RackPeek.Domain/Helpers/Normalize.cs

@@ -10,4 +10,19 @@ public static class Normalize
     {
         return value.Trim().ToLowerInvariant();
     }
+
+    public static string SystemName(string name)
+    {
+        return name.Trim();
+    }
+
+    public static string ServiceName(string name)
+    {
+        return name.Trim();
+    }
+
+    public static string HardwareName(string name)
+    {
+        return name.Trim();
+    }
 }

+ 6 - 5
RackPeek.Domain/Resources/Hardware/AccessPoints/AddAccessPointUseCase.cs

@@ -3,15 +3,16 @@ using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
 
-public class AddAccessPointUseCase(IHardwareRepository repository) : IUseCase
+public class AddAccessPointUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
-
-        var existing = await repository.GetByNameAsync(name);
-        if (existing != null)
-            throw new ConflictException($"Access point '{name}' already exists.");
+        
+        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
+        if (!string.IsNullOrEmpty(existingResourceKind))
+            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
 
         var ap = new AccessPoint
         {

+ 2 - 2
RackPeek.Domain/Resources/Hardware/AccessPoints/DeleteAccessPointUseCase.cs

@@ -8,8 +8,8 @@ public class DeleteAccessPointUseCase(IHardwareRepository repository, ISystemRep
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
-
         var ap = await repository.GetByNameAsync(name);
         if (ap is not AccessPoint)
             throw new NotFoundException($"Access point '{name}' not found.");
@@ -21,7 +21,7 @@ public class DeleteAccessPointUseCase(IHardwareRepository repository, ISystemRep
             systemResource.RunsOn = null;
             await systemsRepo.UpdateAsync(systemResource);
         }
-        
+
         await repository.DeleteAsync(name);
     }
 }

+ 3 - 3
RackPeek.Domain/Resources/Hardware/AccessPoints/DescribeAccessPointUseCase.cs

@@ -11,13 +11,13 @@ public record AccessPointDescription(
 
 public class DescribeAccessPointUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<AccessPointDescription?> ExecuteAsync(string name)
+    public async Task<AccessPointDescription> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
-
         var ap = await repository.GetByNameAsync(name) as AccessPoint;
         if (ap == null)
-            return null;
+            throw new NotFoundException($"Access point '{name}' not found.");
 
         return new AccessPointDescription(
             ap.Name,

+ 8 - 3
RackPeek.Domain/Resources/Hardware/AccessPoints/GetAccessPointUseCase.cs

@@ -5,11 +5,16 @@ namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
 
 public class GetAccessPointUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<AccessPoint?> ExecuteAsync(string name)
+    public async Task<AccessPoint> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
-
         var hardware = await repository.GetByNameAsync(name);
-        return hardware as AccessPoint;
+        if (hardware is not AccessPoint ap)
+        {
+            throw new NotFoundException($"Access point '{name}' not found.");
+        }
+
+        return ap;
     }
 }

+ 1 - 1
RackPeek.Domain/Resources/Hardware/AccessPoints/UpdateAccessPointUseCase.cs

@@ -11,8 +11,8 @@ public class UpdateAccessPointUseCase(IHardwareRepository repository) : IUseCase
         double? speed = null
     )
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
-
         var ap = await repository.GetByNameAsync(name) as AccessPoint;
         if (ap == null)
             throw new NotFoundException($"Access point '{name}' not found.");

+ 5 - 4
RackPeek.Domain/Resources/Hardware/Desktops/AddDesktopUseCase.cs

@@ -3,15 +3,16 @@ using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops;
 
-public class AddDesktopUseCase(IHardwareRepository repository) : IUseCase
+public class AddDesktopUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        var existing = await repository.GetByNameAsync(name);
-        if (existing != null)
-            throw new ConflictException($"Desktop '{name}' already exists.");
+        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
+        if (!string.IsNullOrEmpty(existingResourceKind))
+            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
 
         var desktop = new Desktop
         {

+ 7 - 3
RackPeek.Domain/Resources/Hardware/Desktops/Cpus/AddDesktopCpuUseCase.cs

@@ -1,13 +1,17 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Cpus;
 
 public class AddDesktopCpuUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, Cpu cpu)
+    public async Task ExecuteAsync(string name, Cpu cpu)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new InvalidOperationException($"Desktop '{name}' not found.");
 
         desktop.Cpus ??= new List<Cpu>();
         desktop.Cpus.Add(cpu);

+ 8 - 4
RackPeek.Domain/Resources/Hardware/Desktops/Cpus/RemoveDesktopCpuUseCase.cs

@@ -1,16 +1,20 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Cpus;
 
 public class RemoveDesktopCpuUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, int index)
+    public async Task ExecuteAsync(string name, int index)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         if (desktop.Cpus == null || index < 0 || index >= desktop.Cpus.Count)
-            throw new InvalidOperationException($"CPU index {index} not found on desktop '{desktopName}'.");
+            throw new NotFoundException($"CPU index {index} not found on desktop '{name}'.");
 
         desktop.Cpus.RemoveAt(index);
 

+ 8 - 4
RackPeek.Domain/Resources/Hardware/Desktops/Cpus/UpdateDesktopCpuUseCase.cs

@@ -1,16 +1,20 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Cpus;
 
 public class UpdateDesktopCpuUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, int index, Cpu updated)
+    public async Task ExecuteAsync(string name, int index, Cpu updated)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         if (desktop.Cpus == null || index < 0 || index >= desktop.Cpus.Count)
-            throw new InvalidOperationException($"CPU index {index} not found on desktop '{desktopName}'.");
+            throw new NotFoundException($"CPU index {index} not found on desktop '{name}'.");
 
         desktop.Cpus[index] = updated;
 

+ 2 - 1
RackPeek.Domain/Resources/Hardware/Desktops/DeleteDesktopUseCase.cs

@@ -7,6 +7,7 @@ public class DeleteDesktopUseCase(IHardwareRepository repository, ISystemReposit
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var hardware = await repository.GetByNameAsync(name);
@@ -20,7 +21,7 @@ public class DeleteDesktopUseCase(IHardwareRepository repository, ISystemReposit
             systemResource.RunsOn = null;
             await systemsRepo.UpdateAsync(systemResource);
         }
-        
+
         await repository.DeleteAsync(name);
     }
 }

+ 3 - 2
RackPeek.Domain/Resources/Hardware/Desktops/DescribeDesktopUseCase.cs

@@ -15,13 +15,14 @@ public record DesktopDescription(
 
 public class DescribeDesktopUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<DesktopDescription?> ExecuteAsync(string name)
+    public async Task<DesktopDescription> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var desktop = await repository.GetByNameAsync(name) as Desktop;
         if (desktop == null)
-            return null;
+            throw new NotFoundException($"Desktop '{name}' not found.");
 
         var ramSummary = desktop.Ram == null
             ? "None"

+ 7 - 3
RackPeek.Domain/Resources/Hardware/Desktops/Drives/AddDesktopDriveUseCase.cs

@@ -1,13 +1,17 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Drives;
 
 public class AddDesktopDriveUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, Drive drive)
+    public async Task ExecuteAsync(string name, Drive drive)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         desktop.Drives ??= new List<Drive>();
         desktop.Drives.Add(drive);

+ 8 - 4
RackPeek.Domain/Resources/Hardware/Desktops/Drives/RemoveDesktopDriveUseCase.cs

@@ -1,16 +1,20 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Drives;
 
 public class RemoveDesktopDriveUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, int index)
+    public async Task ExecuteAsync(string name, int index)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         if (desktop.Drives == null || index < 0 || index >= desktop.Drives.Count)
-            throw new InvalidOperationException($"Drive index {index} not found on desktop '{desktopName}'.");
+            throw new NotFoundException($"Drive index {index} not found on desktop '{name}'.");
 
         desktop.Drives.RemoveAt(index);
 

+ 8 - 4
RackPeek.Domain/Resources/Hardware/Desktops/Drives/UpdateDesktopDriveUseCase.cs

@@ -1,16 +1,20 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Drives;
 
 public class UpdateDesktopDriveUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, int index, Drive updated)
+    public async Task ExecuteAsync(string name, int index, Drive updated)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         if (desktop.Drives == null || index < 0 || index >= desktop.Drives.Count)
-            throw new InvalidOperationException($"Drive index {index} not found on desktop '{desktopName}'.");
+            throw new NotFoundException($"Drive index {index} not found on desktop '{name}'.");
 
         desktop.Drives[index] = updated;
 

+ 8 - 2
RackPeek.Domain/Resources/Hardware/Desktops/GetDesktopUseCase.cs

@@ -5,11 +5,17 @@ namespace RackPeek.Domain.Resources.Hardware.Desktops;
 
 public class GetDesktopUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<Desktop?> ExecuteAsync(string name)
+    public async Task<Desktop> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var hardware = await repository.GetByNameAsync(name);
-        return hardware as Desktop;
+        if (hardware is not Desktop desktop)
+        {
+            throw new NotFoundException($"Desktop '{name}' not found.");
+        }
+
+        return desktop;
     }
 }

+ 7 - 3
RackPeek.Domain/Resources/Hardware/Desktops/Gpus/AddDesktopGpuUseCase.cs

@@ -1,13 +1,17 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
 
 public class AddDesktopGpuUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, Gpu gpu)
+    public async Task ExecuteAsync(string name, Gpu gpu)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         desktop.Gpus ??= new List<Gpu>();
         desktop.Gpus.Add(gpu);

+ 8 - 4
RackPeek.Domain/Resources/Hardware/Desktops/Gpus/RemoveDesktopGpuUseCase.cs

@@ -1,16 +1,20 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
 
 public class RemoveDesktopGpuUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, int index)
+    public async Task ExecuteAsync(string name, int index)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         if (desktop.Gpus == null || index < 0 || index >= desktop.Gpus.Count)
-            throw new InvalidOperationException($"GPU index {index} not found on desktop '{desktopName}'.");
+            throw new NotFoundException($"GPU index {index} not found on desktop '{name}'.");
 
         desktop.Gpus.RemoveAt(index);
 

+ 8 - 4
RackPeek.Domain/Resources/Hardware/Desktops/Gpus/UpdateDesktopGpuUseCase.cs

@@ -1,16 +1,20 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
 
 public class UpdateDesktopGpuUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, int index, Gpu updated)
+    public async Task ExecuteAsync(string name, int index, Gpu updated)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         if (desktop.Gpus == null || index < 0 || index >= desktop.Gpus.Count)
-            throw new InvalidOperationException($"GPU index {index} not found on desktop '{desktopName}'.");
+            throw new NotFoundException($"GPU index {index} not found on desktop '{name}'.");
 
         desktop.Gpus[index] = updated;
 

+ 7 - 3
RackPeek.Domain/Resources/Hardware/Desktops/Nics/AddDesktopNicUseCase.cs

@@ -1,13 +1,17 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Nics;
 
 public class AddDesktopNicUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, Nic nic)
+    public async Task ExecuteAsync(string name, Nic nic)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         desktop.Nics ??= new List<Nic>();
         desktop.Nics.Add(nic);

+ 8 - 4
RackPeek.Domain/Resources/Hardware/Desktops/Nics/RemoveDesktopNicUseCase.cs

@@ -1,16 +1,20 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Nics;
 
 public class RemoveDesktopNicUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, int index)
+    public async Task ExecuteAsync(string name, int index)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         if (desktop.Nics == null || index < 0 || index >= desktop.Nics.Count)
-            throw new InvalidOperationException($"NIC index {index} not found on desktop '{desktopName}'.");
+            throw new NotFoundException($"NIC index {index} not found on desktop '{name}'.");
 
         desktop.Nics.RemoveAt(index);
 

+ 8 - 4
RackPeek.Domain/Resources/Hardware/Desktops/Nics/UpdateDesktopNicUseCase.cs

@@ -1,16 +1,20 @@
+using RackPeek.Domain.Helpers;
 using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Desktops.Nics;
 
 public class UpdateDesktopNicUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string desktopName, int index, Nic updated)
+    public async Task ExecuteAsync(string name, int index, Nic updated)
     {
-        var desktop = await repository.GetByNameAsync(desktopName) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{desktopName}' not found.");
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+
+        var desktop = await repository.GetByNameAsync(name) as Desktop
+                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
 
         if (desktop.Nics == null || index < 0 || index >= desktop.Nics.Count)
-            throw new InvalidOperationException($"NIC index {index} not found on desktop '{desktopName}'.");
+            throw new NotFoundException($"NIC index {index} not found on desktop '{name}'.");
 
         desktop.Nics[index] = updated;
 

+ 1 - 0
RackPeek.Domain/Resources/Hardware/Desktops/UpdateDesktopUseCase.cs

@@ -10,6 +10,7 @@ public class UpdateDesktopUseCase(IHardwareRepository repository) : IUseCase
         string? model = null
     )
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var desktop = await repository.GetByNameAsync(name) as Desktop;

+ 5 - 5
RackPeek.Domain/Resources/Hardware/Firewalls/AddFirewallUseCase.cs

@@ -3,16 +3,16 @@ using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Firewalls;
 
-public class AddFirewallUseCase(IHardwareRepository repository) : IUseCase
+public class AddFirewallUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        // basic guard rails
-        var existing = await repository.GetByNameAsync(name);
-        if (existing != null)
-            throw new NotFoundException($"Firewall '{name}' already exists.");
+        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
+        if (!string.IsNullOrEmpty(existingResourceKind))
+            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
 
         var firewallResource = new Firewall
         {

+ 2 - 1
RackPeek.Domain/Resources/Hardware/Firewalls/DeleteFirewallUseCase.cs

@@ -8,6 +8,7 @@ public class DeleteFirewallUseCase(IHardwareRepository repository, ISystemReposi
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         if (await repository.GetByNameAsync(name) is not Firewall hardware)
@@ -20,7 +21,7 @@ public class DeleteFirewallUseCase(IHardwareRepository repository, ISystemReposi
             systemResource.RunsOn = null;
             await systemsRepo.UpdateAsync(systemResource);
         }
-        
+
         await repository.DeleteAsync(name);
     }
 }

+ 4 - 3
RackPeek.Domain/Resources/Hardware/Firewalls/DescribeFirewallUseCase.cs

@@ -15,14 +15,15 @@ public record FirewallDescription(
 
 public class DescribeFirewallUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<FirewallDescription?> ExecuteAsync(string name)
+    public async Task<FirewallDescription> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var firewallResource = await repository.GetByNameAsync(name) as Firewall;
         if (firewallResource == null)
-            return null;
-
+            throw new NotFoundException($"Firewall '{name}' not found.");
+        
         // If no ports exist, return defaults
         var ports = firewallResource.Ports ?? new List<Port>();
 

+ 8 - 2
RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallUseCase.cs

@@ -5,11 +5,17 @@ namespace RackPeek.Domain.Resources.Hardware.Firewalls;
 
 public class GetFirewallUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<Firewall?> ExecuteAsync(string name)
+    public async Task<Firewall> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var hardware = await repository.GetByNameAsync(name);
-        return hardware as Firewall;
+        if (hardware is not Firewall firewall)
+        {
+            throw new NotFoundException($"Firewall '{name}' not found.");
+        }
+        
+        return firewall;
     }
 }

+ 1 - 0
RackPeek.Domain/Resources/Hardware/Firewalls/UpdateFirewallUseCase.cs

@@ -12,6 +12,7 @@ public class UpdateFirewallUseCase(IHardwareRepository repository) : IUseCase
         bool? poe = null
     )
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var firewallResource = await repository.GetByNameAsync(name) as Firewall;

+ 3 - 2
RackPeek.Domain/Resources/Hardware/GetHardwareSystemTreeUseCase.cs

@@ -9,12 +9,13 @@ public class GetHardwareSystemTreeUseCase(
     ISystemRepository systemRepository,
     IServiceRepository serviceRepository) : IUseCase
 {
-    public async Task<HardwareDependencyTree?> ExecuteAsync(string hardwareName)
+    public async Task<HardwareDependencyTree> ExecuteAsync(string hardwareName)
     {
         ThrowIfInvalid.ResourceName(hardwareName);
 
         var server = await hardwareRepository.GetByNameAsync(hardwareName);
-        if (server is null) return null;
+        if (server is null) 
+            throw new NotFoundException($"Hardware '{hardwareName}' not found.");
 
         return await BuildDependencyTreeAsync(server);
     }

+ 5 - 4
RackPeek.Domain/Resources/Hardware/Laptops/AddLaptopUseCase.cs

@@ -3,15 +3,16 @@ using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Laptops;
 
-public class AddLaptopUseCase(IHardwareRepository repository) : IUseCase
+public class AddLaptopUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        var existing = await repository.GetByNameAsync(name);
-        if (existing != null)
-            throw new InvalidOperationException($"Laptop '{name}' already exists.");
+        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
+        if (!string.IsNullOrEmpty(existingResourceKind))
+            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
 
         var laptop = new Laptop
         {

+ 1 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Cpus/AddDesktopCpuUseCase.cs

@@ -7,6 +7,7 @@ public class AddLaptopCpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name, Cpu cpu)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
         var laptop = await repository.GetByNameAsync(name) as Laptop
                      ?? throw new InvalidOperationException($"Laptop '{name}' not found.");

+ 1 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Cpus/RemoveDesktopCpuUseCase.cs

@@ -7,6 +7,7 @@ public class RemoveLaptopCpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name, int index)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var laptop = await repository.GetByNameAsync(name) as Laptop

+ 3 - 2
RackPeek.Domain/Resources/Hardware/Laptops/Cpus/UpdateDesktopCpuUseCase.cs

@@ -7,12 +7,13 @@ public class UpdateLaptopCpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name, int index, Cpu updated)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
         var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
+                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
 
         if (laptop.Cpus == null || index < 0 || index >= laptop.Cpus.Count)
-            throw new InvalidOperationException($"CPU index {index} not found on Laptop '{name}'.");
+            throw new NotFoundException($"CPU index {index} not found on Laptop '{name}'.");
 
         laptop.Cpus[index] = updated;
 

+ 2 - 1
RackPeek.Domain/Resources/Hardware/Laptops/DeleteLaptopUseCase.cs

@@ -7,6 +7,7 @@ public class DeleteLaptopUseCase(IHardwareRepository repository, ISystemReposito
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var hardware = await repository.GetByNameAsync(name);
@@ -20,7 +21,7 @@ public class DeleteLaptopUseCase(IHardwareRepository repository, ISystemReposito
             systemResource.RunsOn = null;
             await systemsRepo.UpdateAsync(systemResource);
         }
-        
+
         await repository.DeleteAsync(name);
     }
 }

+ 3 - 2
RackPeek.Domain/Resources/Hardware/Laptops/DescribeLaptopUseCase.cs

@@ -5,13 +5,14 @@ namespace RackPeek.Domain.Resources.Hardware.Laptops;
 
 public class DescribeLaptopUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<LaptopDescription?> ExecuteAsync(string name)
+    public async Task<LaptopDescription> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var laptop = await repository.GetByNameAsync(name) as Laptop;
         if (laptop == null)
-            return null;
+            throw new NotFoundException($"Laptop '{name}' not found.");
 
         var ramSummary = laptop.Ram == null
             ? "None"

+ 2 - 1
RackPeek.Domain/Resources/Hardware/Laptops/Drives/AddDesktopDriveUseCase.cs

@@ -7,10 +7,11 @@ public class AddLaptopDriveUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name, Drive drive)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
+                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
 
         laptop.Drives ??= new List<Drive>();
         laptop.Drives.Add(drive);

+ 3 - 2
RackPeek.Domain/Resources/Hardware/Laptops/Drives/RemoveDesktopDriveUseCase.cs

@@ -7,12 +7,13 @@ public class RemoveLaptopDriveUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name, int index)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
         var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
+                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
 
         if (laptop.Drives == null || index < 0 || index >= laptop.Drives.Count)
-            throw new InvalidOperationException($"Drive index {index} not found on Laptop '{name}'.");
+            throw new NotFoundException($"Drive index {index} not found on Laptop '{name}'.");
 
         laptop.Drives.RemoveAt(index);
 

+ 3 - 2
RackPeek.Domain/Resources/Hardware/Laptops/Drives/UpdateDesktopDriveUseCase.cs

@@ -7,13 +7,14 @@ public class UpdateLaptopDriveUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name, int index, Drive updated)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
+                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
 
         if (laptop.Drives == null || index < 0 || index >= laptop.Drives.Count)
-            throw new InvalidOperationException($"Drive index {index} not found on Laptop '{name}'.");
+            throw new NotFoundException($"Drive index {index} not found on Laptop '{name}'.");
 
         laptop.Drives[index] = updated;
 

+ 8 - 2
RackPeek.Domain/Resources/Hardware/Laptops/GetDesktopUseCase.cs

@@ -5,11 +5,17 @@ namespace RackPeek.Domain.Resources.Hardware.Laptops;
 
 public class GetLaptopUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<Laptop?> ExecuteAsync(string name)
+    public async Task<Laptop> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var hardware = await repository.GetByNameAsync(name);
-        return hardware as Laptop;
+        if (hardware is not Laptop laptop)
+        {
+            throw new NotFoundException($"Laptop '{name}' not found.");
+        }
+
+        return laptop;
     }
 }

+ 1 - 0
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/AddDesktopGpuUseCase.cs

@@ -7,6 +7,7 @@ public class AddLaptopGpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name, Gpu gpu)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
         var laptop = await repository.GetByNameAsync(name) as Laptop
                      ?? throw new InvalidOperationException($"Laptop '{name}' not found.");

+ 3 - 2
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/RemoveDesktopGpuUseCase.cs

@@ -7,12 +7,13 @@ public class RemoveLaptopGpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name, int index)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
         var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
+                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
 
         if (laptop.Gpus == null || index < 0 || index >= laptop.Gpus.Count)
-            throw new InvalidOperationException($"GPU index {index} not found on Laptop '{name}'.");
+            throw new NotFoundException($"GPU index {index} not found on Laptop '{name}'.");
 
         laptop.Gpus.RemoveAt(index);
 

+ 3 - 2
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/UpdateDesktopGpuUseCase.cs

@@ -7,13 +7,14 @@ public class UpdateLaptopGpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name, int index, Gpu updated)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
+                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
 
         if (laptop.Gpus == null || index < 0 || index >= laptop.Gpus.Count)
-            throw new InvalidOperationException($"GPU index {index} not found on Laptop '{name}'.");
+            throw new NotFoundException($"GPU index {index} not found on Laptop '{name}'.");
 
         laptop.Gpus[index] = updated;
 

+ 5 - 5
RackPeek.Domain/Resources/Hardware/Routers/AddRouterUseCase.cs

@@ -3,16 +3,16 @@ using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Routers;
 
-public class AddRouterUseCase(IHardwareRepository repository) : IUseCase
+public class AddRouterUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        // basic guard rails
-        var existing = await repository.GetByNameAsync(name);
-        if (existing != null)
-            throw new ConflictException($"Router '{name}' already exists.");
+        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
+        if (!string.IsNullOrEmpty(existingResourceKind))
+            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
 
         var routerResource = new Router
         {

+ 2 - 1
RackPeek.Domain/Resources/Hardware/Routers/DeleteRouterUseCase.cs

@@ -8,6 +8,7 @@ public class DeleteRouterUseCase(IHardwareRepository repository, ISystemReposito
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         if (await repository.GetByNameAsync(name) is not Router hardware)
@@ -20,7 +21,7 @@ public class DeleteRouterUseCase(IHardwareRepository repository, ISystemReposito
             systemResource.RunsOn = null;
             await systemsRepo.UpdateAsync(systemResource);
         }
-        
+
         await repository.DeleteAsync(name);
     }
 }

+ 3 - 2
RackPeek.Domain/Resources/Hardware/Routers/DescribeRouterUseCase.cs

@@ -15,13 +15,14 @@ public record RouterDescription(
 
 public class DescribeRouterUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<RouterDescription?> ExecuteAsync(string name)
+    public async Task<RouterDescription> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var routerResource = await repository.GetByNameAsync(name) as Router;
         if (routerResource == null)
-            return null;
+            throw new NotFoundException($"Router '{name}' not found.");
 
         // If no ports exist, return defaults
         var ports = routerResource.Ports ?? new List<Port>();

+ 8 - 2
RackPeek.Domain/Resources/Hardware/Routers/GetRouterUseCase.cs

@@ -5,11 +5,17 @@ namespace RackPeek.Domain.Resources.Hardware.Routers;
 
 public class GetRouterUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<Router?> ExecuteAsync(string name)
+    public async Task<Router> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var hardware = await repository.GetByNameAsync(name);
-        return hardware as Router;
+        if (hardware is not Router router)
+        {
+            throw new NotFoundException($"Router '{name}' not found.");
+        }
+        
+        return router;
     }
 }

+ 1 - 0
RackPeek.Domain/Resources/Hardware/Routers/UpdateRouterUseCase.cs

@@ -12,6 +12,7 @@ public class UpdateRouterUseCase(IHardwareRepository repository) : IUseCase
         bool? poe = null
     )
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var routerResource = await repository.GetByNameAsync(name) as Router;

+ 5 - 5
RackPeek.Domain/Resources/Hardware/Servers/AddServerUseCase.cs

@@ -3,16 +3,16 @@ using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Servers;
 
-public class AddServerUseCase(IHardwareRepository repository) : IUseCase
+public class AddServerUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        // basic guard rails
-        var existing = await repository.GetByNameAsync(name);
-        if (existing != null)
-            throw new ConflictException($"Resource: '{name}' already exists.");
+        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
+        if (!string.IsNullOrEmpty(existingResourceKind))
+            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
 
         var server = new Server
         {

+ 4 - 3
RackPeek.Domain/Resources/Hardware/Servers/Cpus/AddCpuUseCase.cs

@@ -6,14 +6,15 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Cpus;
 public class AddCpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(
-        string serverName,
+        string name,
         string model,
         int cores,
         int threads)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
 
         if (hardware is not Server server) return;
 

+ 4 - 3
RackPeek.Domain/Resources/Hardware/Servers/Cpus/RemoveCpuUseCase.cs

@@ -6,12 +6,13 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Cpus;
 public class RemoveCpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(
-        string serverName,
+        string name,
         int index)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
         if (hardware is not Server server) return;
 
         server.Cpus ??= [];

+ 4 - 3
RackPeek.Domain/Resources/Hardware/Servers/Cpus/UpdateCpuUseCase.cs

@@ -6,15 +6,16 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Cpus;
 public class UpdateCpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(
-        string serverName,
+        string name,
         int index,
         string model,
         int cores,
         int threads)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
 
         if (hardware is not Server server) return;
 

+ 2 - 1
RackPeek.Domain/Resources/Hardware/Servers/DeleteServerUseCase.cs

@@ -8,6 +8,7 @@ public class DeleteServerUseCase(IHardwareRepository repository, ISystemReposito
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var hardware = await repository.GetByNameAsync(name) as Server;
@@ -21,7 +22,7 @@ public class DeleteServerUseCase(IHardwareRepository repository, ISystemReposito
             systemResource.RunsOn = null;
             await systemsRepo.UpdateAsync(systemResource);
         }
-        
+
         await repository.DeleteAsync(name);
     }
 }

+ 3 - 2
RackPeek.Domain/Resources/Hardware/Servers/DescribeServerUseCase.cs

@@ -16,13 +16,14 @@ public record ServerDescription(
 
 public class DescribeServerUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<ServerDescription?> ExecuteAsync(string name)
+    public async Task<ServerDescription> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var server = await repository.GetByNameAsync(name) as Server;
         if (server == null)
-            return null;
+            throw new NotFoundException($"Server '{name}' not found.");
 
         var cpuSummary = server.Cpus == null
             ? "Unknown"

+ 4 - 3
RackPeek.Domain/Resources/Hardware/Servers/Drives/AddDriveUseCase.cs

@@ -6,13 +6,14 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Drives;
 public class AddDrivesUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(
-        string serverName,
+        string name,
         string type,
         int size)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
 
         if (hardware is not Server server) return;
 

+ 4 - 3
RackPeek.Domain/Resources/Hardware/Servers/Drives/RemoveDriveUseCase.cs

@@ -5,11 +5,12 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Drives;
 
 public class RemoveDriveUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string serverName, int index)
+    public async Task ExecuteAsync(string name, int index)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
         if (hardware is not Server server) return;
         server.Drives ??= [];
         if (index < 0 || index >= server.Drives.Count)

+ 4 - 3
RackPeek.Domain/Resources/Hardware/Servers/Drives/UpdateDriveUseCase.cs

@@ -5,11 +5,12 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Drives;
 
 public class UpdateDriveUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string serverName, int index, string type, int size)
+    public async Task ExecuteAsync(string name, int index, string type, int size)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
         if (hardware is not Server server) return;
 
         server.Drives ??= [];

+ 8 - 2
RackPeek.Domain/Resources/Hardware/Servers/GetServerUseCase.cs

@@ -5,10 +5,16 @@ namespace RackPeek.Domain.Resources.Hardware.Servers;
 
 public class GetServerUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<Server?> ExecuteAsync(string name)
+    public async Task<Server> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
         var hardware = await repository.GetByNameAsync(name);
-        return hardware as Server;
+        if (hardware is not Server server)
+        {
+            throw new NotFoundException($"Server '{name}' not found.");
+        }
+
+        return server;
     }
 }

+ 5 - 4
RackPeek.Domain/Resources/Hardware/Servers/Gpus/AddGpuUseCase.cs

@@ -6,16 +6,17 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Gpus;
 public class AddGpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(
-        string serverName,
+        string name,
         string model,
         int vram)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
 
         if (hardware is not Server server)
-            throw new NotFoundException($"Server '{serverName}' not found.");
+            throw new NotFoundException($"Server '{name}' not found.");
 
         server.Gpus ??= [];
 

+ 4 - 3
RackPeek.Domain/Resources/Hardware/Servers/Gpus/RemoveGpuUseCase.cs

@@ -5,11 +5,12 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Gpus;
 
 public class RemoveGpuUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string serverName, int index)
+    public async Task ExecuteAsync(string name, int index)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
 
         if (hardware is not Server server)
             return;

+ 4 - 3
RackPeek.Domain/Resources/Hardware/Servers/Gpus/UpdateGpuUseCase.cs

@@ -6,14 +6,15 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Gpus;
 public class UpdateGpuUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(
-        string serverName,
+        string name,
         int index,
         string model,
         int vram)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
 
         if (hardware is not Server server)
             return;

+ 5 - 4
RackPeek.Domain/Resources/Hardware/Servers/Nics/AddNicUseCase.cs

@@ -6,22 +6,23 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Nics;
 public class AddNicUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(
-        string serverName,
+        string name,
         string type,
         int speed,
         int ports)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
         ThrowIfInvalid.NicSpeed(speed);
         ThrowIfInvalid.NicPorts(ports);
 
         var nicType = Normalize.NicType(type);
         ThrowIfInvalid.NicType(nicType);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
 
         if (hardware is not Server server)
-            throw new NotFoundException($"Server: '{serverName}' not found.");
+            throw new NotFoundException($"Server: '{name}' not found.");
 
         server.Nics ??= [];
 

+ 5 - 4
RackPeek.Domain/Resources/Hardware/Servers/Nics/RemoveNicUseCase.cs

@@ -6,13 +6,14 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Nics;
 
 public class RemoveNicUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task ExecuteAsync(string serverName, int index)
+    public async Task ExecuteAsync(string name, int index)
     {
-        ThrowIfInvalid.ResourceName(serverName);
-        var hardware = await repository.GetByNameAsync(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
+        var hardware = await repository.GetByNameAsync(name);
 
         if (hardware is not Server server)
-            throw new NotFoundException($"Server: '{serverName}' not found.");
+            throw new NotFoundException($"Server: '{name}' not found.");
 
         server.Nics ??= [];
 

+ 5 - 4
RackPeek.Domain/Resources/Hardware/Servers/Nics/UpdateNicUseCase.cs

@@ -7,23 +7,24 @@ namespace RackPeek.Domain.Resources.Hardware.Servers.Nics;
 public class UpdateNicUseCase(IHardwareRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(
-        string serverName,
+        string name,
         int index,
         string type,
         int speed,
         int ports)
     {
-        ThrowIfInvalid.ResourceName(serverName);
+        name = Normalize.HardwareName(name);
+        ThrowIfInvalid.ResourceName(name);
         ThrowIfInvalid.NicSpeed(speed);
         ThrowIfInvalid.NicPorts(ports);
 
         var nicType = Normalize.NicType(type);
         ThrowIfInvalid.NicType(nicType);
 
-        var hardware = await repository.GetByNameAsync(serverName);
+        var hardware = await repository.GetByNameAsync(name);
 
         if (hardware is not Server server)
-            throw new NotFoundException($"Server: '{serverName}' not found.");
+            throw new NotFoundException($"Server: '{name}' not found.");
 
         server.Nics ??= [];
 

+ 1 - 0
RackPeek.Domain/Resources/Hardware/Servers/UpdateServerUseCase.cs

@@ -11,6 +11,7 @@ public class UpdateServerUseCase(IHardwareRepository repository) : IUseCase
         bool? ipmi = null
     )
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var server = await repository.GetByNameAsync(name) as Server;

+ 5 - 5
RackPeek.Domain/Resources/Hardware/Switches/AddSwitchUseCase.cs

@@ -3,16 +3,16 @@ using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.Switches;
 
-public class AddSwitchUseCase(IHardwareRepository repository) : IUseCase
+public class AddSwitchUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        // basic guard rails
-        var existing = await repository.GetByNameAsync(name);
-        if (existing != null)
-            throw new InvalidOperationException($"Switch '{name}' already exists.");
+        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
+        if (!string.IsNullOrEmpty(existingResourceKind))
+            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
 
         var switchResource = new Switch
         {

+ 2 - 1
RackPeek.Domain/Resources/Hardware/Switches/DeleteSwitchUseCase.cs

@@ -8,6 +8,7 @@ public class DeleteSwitchUseCase(IHardwareRepository repository, ISystemReposito
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         if (await repository.GetByNameAsync(name) is not Switch hardware)
@@ -20,7 +21,7 @@ public class DeleteSwitchUseCase(IHardwareRepository repository, ISystemReposito
             systemResource.RunsOn = null;
             await systemsRepo.UpdateAsync(systemResource);
         }
-        
+
         await repository.DeleteAsync(name);
     }
 }

+ 4 - 3
RackPeek.Domain/Resources/Hardware/Switches/DescribeSwitchUseCase.cs

@@ -15,14 +15,15 @@ public record SwitchDescription(
 
 public class DescribeSwitchUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<SwitchDescription?> ExecuteAsync(string name)
+    public async Task<SwitchDescription> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var switchResource = await repository.GetByNameAsync(name) as Switch;
         if (switchResource == null)
-            return null;
-
+            throw new NotFoundException($"Switch '{name}' not found.");
+        
         // If no ports exist, return defaults
         var ports = switchResource.Ports ?? new List<Port>();
 

+ 8 - 2
RackPeek.Domain/Resources/Hardware/Switches/GetSwitchUseCase.cs

@@ -5,11 +5,17 @@ namespace RackPeek.Domain.Resources.Hardware.Switches;
 
 public class GetSwitchUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<Switch?> ExecuteAsync(string name)
+    public async Task<Switch> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var hardware = await repository.GetByNameAsync(name);
-        return hardware as Switch;
+        if (hardware is not Switch _switch)
+        {
+            throw new NotFoundException($"Switch '{name}' not found.");
+        }
+        
+        return _switch;
     }
 }

+ 1 - 0
RackPeek.Domain/Resources/Hardware/Switches/UpdateSwitchUseCase.cs

@@ -12,6 +12,7 @@ public class UpdateSwitchUseCase(IHardwareRepository repository) : IUseCase
         bool? poe = null
     )
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var switchResource = await repository.GetByNameAsync(name) as Switch;

+ 5 - 4
RackPeek.Domain/Resources/Hardware/UpsUnits/AddUpsUseCase.cs

@@ -3,15 +3,16 @@ using RackPeek.Domain.Resources.Hardware.Models;
 
 namespace RackPeek.Domain.Resources.Hardware.UpsUnits;
 
-public class AddUpsUseCase(IHardwareRepository repository) : IUseCase
+public class AddUpsUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
-        var existing = await repository.GetByNameAsync(name);
-        if (existing != null)
-            throw new InvalidOperationException($"UPS '{name}' already exists.");
+        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
+        if (!string.IsNullOrEmpty(existingResourceKind))
+            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
 
         var ups = new Ups
         {

+ 2 - 1
RackPeek.Domain/Resources/Hardware/UpsUnits/DeleteUpsUseCase.cs

@@ -8,6 +8,7 @@ public class DeleteUpsUseCase(IHardwareRepository repository, ISystemRepository
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         if (await repository.GetByNameAsync(name) is not Ups ups)
@@ -20,7 +21,7 @@ public class DeleteUpsUseCase(IHardwareRepository repository, ISystemRepository
             systemResource.RunsOn = null;
             await systemsRepo.UpdateAsync(systemResource);
         }
-        
+
         await repository.DeleteAsync(name);
     }
 }

+ 3 - 2
RackPeek.Domain/Resources/Hardware/UpsUnits/DescribeUpsUseCase.cs

@@ -11,13 +11,14 @@ public record UpsDescription(
 
 public class DescribeUpsUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<UpsDescription?> ExecuteAsync(string name)
+    public async Task<UpsDescription> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var ups = await repository.GetByNameAsync(name) as Ups;
         if (ups == null)
-            return null;
+            throw new NotFoundException($"Ups '{name}' not found.");
 
         return new UpsDescription(
             ups.Name,

+ 7 - 2
RackPeek.Domain/Resources/Hardware/UpsUnits/GetUpsUnitUseCase.cs

@@ -5,11 +5,16 @@ namespace RackPeek.Domain.Resources.Hardware.UpsUnits;
 
 public class GetUpsUnitUseCase(IHardwareRepository repository) : IUseCase
 {
-    public async Task<Ups?> ExecuteAsync(string name)
+    public async Task<Ups> ExecuteAsync(string name)
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var hardware = await repository.GetByNameAsync(name);
-        return hardware as Ups;
+        if (hardware is not Ups ups)
+        {
+            throw new NotFoundException($"Ups '{name}' not found.");
+        }
+        return ups;
     }
 }

+ 1 - 0
RackPeek.Domain/Resources/Hardware/UpsUnits/UpdateUpsUseCase.cs

@@ -11,6 +11,7 @@ public class UpdateUpsUseCase(IHardwareRepository repository) : IUseCase
         int? va = null
     )
     {
+        name = Normalize.HardwareName(name);
         ThrowIfInvalid.ResourceName(name);
 
         var ups = await repository.GetByNameAsync(name) as Ups;

+ 8 - 0
RackPeek.Domain/Resources/IResourceRepository.cs

@@ -0,0 +1,8 @@
+namespace RackPeek.Domain.Resources;
+
+public interface IResourceRepository
+{
+    public Task<string?> GetResourceKindAsync(string name);
+    public Task<bool> ResourceExistsAsync(string name);
+
+}

+ 6 - 5
RackPeek.Domain/Resources/Services/UseCases/AddServiceUseCase.cs

@@ -2,15 +2,16 @@ using RackPeek.Domain.Helpers;
 
 namespace RackPeek.Domain.Resources.Services.UseCases;
 
-public class AddServiceUseCase(IServiceRepository repository) : IUseCase
+public class AddServiceUseCase(IServiceRepository repository, IResourceRepository resourceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.ServiceName(name);
         ThrowIfInvalid.ResourceName(name);
-        // basic guard rails
-        var existing = await repository.GetByNameAsync(name);
-        if (existing != null)
-            throw new InvalidOperationException($"Service '{name}' already exists.");
+
+        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
+        if (!string.IsNullOrEmpty(existingResourceKind))
+            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
 
         var service = new Service
         {

+ 1 - 0
RackPeek.Domain/Resources/Services/UseCases/DeleteServiceUseCase.cs

@@ -6,6 +6,7 @@ public class DeleteServiceUseCase(IServiceRepository repository) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.ServiceName(name);
         ThrowIfInvalid.ResourceName(name);
         if (await repository.GetByNameAsync(name) is not Service)
             throw new InvalidOperationException($"Service '{name}' not found.");

+ 3 - 2
RackPeek.Domain/Resources/Services/UseCases/DescribeServiceUseCase.cs

@@ -15,12 +15,13 @@ public record ServiceDescription(
 
 public class DescribeServiceUseCase(IServiceRepository repository, ISystemRepository systemRepo) : IUseCase
 {
-    public async Task<ServiceDescription?> ExecuteAsync(string name)
+    public async Task<ServiceDescription> ExecuteAsync(string name)
     {
+        name = Normalize.ServiceName(name);
         ThrowIfInvalid.ResourceName(name);
         var service = await repository.GetByNameAsync(name);
         if (service is null)
-            return null;
+            throw new NotFoundException($"Service '{name}' not found.");
 
         string? runsOnPhysicalHost = null;
         if (!string.IsNullOrEmpty(service.RunsOn))

+ 10 - 2
RackPeek.Domain/Resources/Services/UseCases/GetServiceUseCase.cs

@@ -4,9 +4,17 @@ namespace RackPeek.Domain.Resources.Services.UseCases;
 
 public class GetServiceUseCase(IServiceRepository repository) : IUseCase
 {
-    public async Task<Service?> ExecuteAsync(string name)
+    public async Task<Service> ExecuteAsync(string name)
     {
+        name = Normalize.ServiceName(name);
         ThrowIfInvalid.ResourceName(name);
-        return await repository.GetByNameAsync(name);
+        var resource = await repository.GetByNameAsync(name);
+
+        if (resource is null)
+        {
+            throw new NotFoundException($"Service '{name}' not found.");
+        }
+        
+        return resource;
     }
 }

+ 9 - 2
RackPeek.Domain/Resources/Services/UseCases/UpdateServiceUseCase.cs

@@ -1,8 +1,9 @@
 using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Services.UseCases;
 
-public class UpdateServiceUseCase(IServiceRepository repository) : IUseCase
+public class UpdateServiceUseCase(IServiceRepository repository, ISystemRepository systemRepo) : IUseCase
 {
     public async Task ExecuteAsync(
         string name,
@@ -13,10 +14,11 @@ public class UpdateServiceUseCase(IServiceRepository repository) : IUseCase
         string? runsOn = null
     )
     {
+        name = Normalize.ServiceName(name);
         ThrowIfInvalid.ResourceName(name);
         var service = await repository.GetByNameAsync(name);
         if (service is null)
-            throw new InvalidOperationException($"Service '{name}' not found.");
+            throw new NotFoundException($"Service '{name}' not found.");
 
         if (!string.IsNullOrWhiteSpace(ip))
         {
@@ -43,7 +45,12 @@ public class UpdateServiceUseCase(IServiceRepository repository) : IUseCase
         }
 
         if (!string.IsNullOrWhiteSpace(runsOn))
+        {
+            ThrowIfInvalid.ResourceName(runsOn);
+            var parentSystem = await systemRepo.GetByNameAsync(runsOn);
+            if (parentSystem == null) throw new NotFoundException($"Parent system '{runsOn}' not found.");
             service.RunsOn = runsOn;
+        }
 
         await repository.UpdateAsync(service);
     }

+ 6 - 5
RackPeek.Domain/Resources/SystemResources/UseCases/AddSystemUseCase.cs

@@ -2,15 +2,16 @@ using RackPeek.Domain.Helpers;
 
 namespace RackPeek.Domain.Resources.SystemResources.UseCases;
 
-public class AddSystemUseCase(ISystemRepository repository) : IUseCase
+public class AddSystemUseCase(ISystemRepository repository, IResourceRepository resourceRepo) : IUseCase
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.SystemName(name);
         ThrowIfInvalid.ResourceName(name);
-        // basic guard rails
-        var existing = await repository.GetByNameAsync(name);
-        if (existing != null)
-            throw new InvalidOperationException($"System '{name}' already exists.");
+
+        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
+        if (!string.IsNullOrEmpty(existingResourceKind))
+            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
 
         var system = new SystemResource
         {

+ 4 - 3
RackPeek.Domain/Resources/SystemResources/UseCases/DeleteSystemUseCase.cs

@@ -7,11 +7,12 @@ public class DeleteSystemUseCase(ISystemRepository repository, IServiceRepositor
 {
     public async Task ExecuteAsync(string name)
     {
+        name = Normalize.SystemName(name);
         ThrowIfInvalid.ResourceName(name);
-        
+
         if (await repository.GetByNameAsync(name) is not SystemResource)
             throw new NotFoundException($"System '{name}' not found.");
-        
+
         // Break link to dependants
         var dependants = await serviceRepo.GetBySystemHostAsync(name);
         foreach (var serviceResource in dependants)
@@ -19,7 +20,7 @@ public class DeleteSystemUseCase(ISystemRepository repository, IServiceRepositor
             serviceResource.RunsOn = null;
             await serviceRepo.UpdateAsync(serviceResource);
         }
-        
+
         await repository.DeleteAsync(name);
     }
 }

+ 3 - 2
RackPeek.Domain/Resources/SystemResources/UseCases/DescribeSystemUseCase.cs

@@ -14,12 +14,13 @@ public record SystemDescription(
 
 public class DescribeSystemUseCase(ISystemRepository repository) : IUseCase
 {
-    public async Task<SystemDescription?> ExecuteAsync(string name)
+    public async Task<SystemDescription> ExecuteAsync(string name)
     {
+        name = Normalize.SystemName(name);
         ThrowIfInvalid.ResourceName(name);
         var system = await repository.GetByNameAsync(name);
         if (system is null)
-            return null;
+            throw new NotFoundException($"System '{name}' not found.");
 
         return new SystemDescription(
             system.Name,

+ 8 - 5
RackPeek.Domain/Resources/SystemResources/UseCases/GetSystemServiceTreeUseCase.cs

@@ -8,12 +8,15 @@ public class GetSystemServiceTreeUseCase(
     ISystemRepository systemRepository,
     IServiceRepository serviceRepository) : IUseCase
 {
-    public async Task<SystemDependencyTree?> ExecuteAsync(string systemName)
+    public async Task<SystemDependencyTree> ExecuteAsync(string name)
     {
-        ThrowIfInvalid.ResourceName(systemName);
-        var system = await systemRepository.GetByNameAsync(systemName);
-        if (system is null) return null;
-
+        name = Normalize.SystemName(name);
+        ThrowIfInvalid.ResourceName(name);
+        var system = await systemRepository.GetByNameAsync(name);
+        if (system is null)
+        {
+            throw new NotFoundException($"System '{name}' not found.");
+        }
         var services = await serviceRepository.GetBySystemHostAsync(system.Name);
 
         return new SystemDependencyTree(system, services);

+ 10 - 2
RackPeek.Domain/Resources/SystemResources/UseCases/GetSystemUseCase.cs

@@ -4,9 +4,17 @@ namespace RackPeek.Domain.Resources.SystemResources.UseCases;
 
 public class GetSystemUseCase(ISystemRepository repository) : IUseCase
 {
-    public async Task<SystemResource?> ExecuteAsync(string name)
+    public async Task<SystemResource> ExecuteAsync(string name)
     {
+        name = Normalize.SystemName(name);
         ThrowIfInvalid.ResourceName(name);
-        return await repository.GetByNameAsync(name);
+        var system = await repository.GetByNameAsync(name);
+
+        if (system == null)
+        {
+            throw new NotFoundException($"System '{name}' not found.");
+        }
+        
+        return system;
     }
 }

+ 9 - 1
RackPeek.Domain/Resources/SystemResources/UseCases/UpdateSystemUseCase.cs

@@ -1,8 +1,9 @@
 using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Resources.Hardware;
 
 namespace RackPeek.Domain.Resources.SystemResources.UseCases;
 
-public class UpdateSystemUseCase(ISystemRepository repository) : IUseCase
+public class UpdateSystemUseCase(ISystemRepository repository, IHardwareRepository hardwareRepo) : IUseCase
 {
     public async Task ExecuteAsync(
         string name,
@@ -13,6 +14,7 @@ public class UpdateSystemUseCase(ISystemRepository repository) : IUseCase
         string? runsOn = null
     )
     {
+        name = Normalize.SystemName(name);
         ThrowIfInvalid.ResourceName(name);
         var system = await repository.GetByNameAsync(name);
         if (system is null)
@@ -31,7 +33,13 @@ public class UpdateSystemUseCase(ISystemRepository repository) : IUseCase
             system.Ram = ram.Value;
 
         if (!string.IsNullOrWhiteSpace(runsOn))
+        {
+            ThrowIfInvalid.ResourceName(runsOn);
+            var parentHardware = await hardwareRepo.GetByNameAsync(runsOn);
+            if (parentHardware == null) throw new NotFoundException($"Parent hardware '{runsOn}' not found.");
             system.RunsOn = runsOn;
+        }
+
 
         await repository.UpdateAsync(system);
     }

BIN
RackPeek.Web/config/.DS_Store


+ 3 - 1
RackPeek/CliBootstrap.cs

@@ -24,6 +24,7 @@ using RackPeek.Commands.Systems;
 using RackPeek.Commands.Ups;
 using RackPeek.Domain;
 using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Resources;
 using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Services;
 using RackPeek.Domain.Resources.SystemResources;
@@ -69,7 +70,8 @@ public static class CliBootstrap
         services.AddScoped<IHardwareRepository>(_ => new YamlHardwareRepository(collection));
         services.AddScoped<ISystemRepository>(_ => new YamlSystemRepository(collection));
         services.AddScoped<IServiceRepository>(_ => new YamlServiceRepository(collection));
-
+        services.AddScoped<IResourceRepository>(_ => new YamlResourceRepository(collection));
+        
         // Application
         services.AddUseCases();
         services.AddCommands();

+ 1 - 7
RackPeek/Commands/AccessPoints/AccessPointDescribeCommand.cs

@@ -18,13 +18,7 @@ public class AccessPointDescribeCommand(
         var useCase = scope.ServiceProvider.GetRequiredService<DescribeAccessPointUseCase>();
 
         var ap = await useCase.ExecuteAsync(settings.Name);
-
-        if (ap == null)
-        {
-            AnsiConsole.MarkupLine($"[red]Access Point '{settings.Name}' not found.[/]");
-            return 1;
-        }
-
+        
         var grid = new Grid()
             .AddColumn(new GridColumn().NoWrap())
             .AddColumn(new GridColumn().NoWrap());

+ 0 - 6
RackPeek/Commands/AccessPoints/AccessPointGetByNameCommand.cs

@@ -19,12 +19,6 @@ public class AccessPointGetByNameCommand(
 
         var ap = await useCase.ExecuteAsync(settings.Name);
 
-        if (ap == null)
-        {
-            AnsiConsole.MarkupLine($"[red]Access Point '{settings.Name}' not found.[/]");
-            return 1;
-        }
-
         AnsiConsole.MarkupLine(
             $"[green]{ap.Name}[/]  Model: {ap.Model ?? "Unknown"}, Speed: {ap.Speed?.ToString() ?? "Unknown"}Gbps");
 

+ 1 - 7
RackPeek/Commands/Desktops/DesktopDescribeCommand.cs

@@ -17,13 +17,7 @@ public class DesktopDescribeCommand(IServiceProvider provider)
         var useCase = scope.ServiceProvider.GetRequiredService<DescribeDesktopUseCase>();
 
         var result = await useCase.ExecuteAsync(settings.Name);
-
-        if (result == null)
-        {
-            AnsiConsole.MarkupLine($"[red]Desktop '{settings.Name}' not found.[/]");
-            return 1;
-        }
-
+        
         var grid = new Grid().AddColumn().AddColumn();
 
         grid.AddRow("Name:", result.Name);

+ 0 - 6
RackPeek/Commands/Desktops/DesktopGetByNameCommand.cs

@@ -18,12 +18,6 @@ public class DesktopGetByNameCommand(IServiceProvider provider)
 
         var desktop = await useCase.ExecuteAsync(settings.Name);
 
-        if (desktop == null)
-        {
-            AnsiConsole.MarkupLine($"[red]Desktop '{settings.Name}' not found.[/]");
-            return 1;
-        }
-
         AnsiConsole.MarkupLine($"[green]{desktop.Name}[/] (Model: {desktop.Model ?? "Unknown"})");
         return 0;
     }

+ 0 - 6
RackPeek/Commands/Desktops/DesktopTreeCommand.cs

@@ -14,12 +14,6 @@ public sealed class DesktopTreeCommand(GetHardwareSystemTreeUseCase useCase)
     {
         var tree = await useCase.ExecuteAsync(settings.Name);
 
-        if (tree is null)
-        {
-            AnsiConsole.MarkupLine($"[red]Error:[/] Desktop '{settings.Name}' not found.");
-            return -1;
-        }
-
         var root = new Tree($"[bold]{tree.Hardware.Name}[/]");
 
         foreach (var system in tree.Systems)

+ 0 - 6
RackPeek/Commands/Firewalls/FirewallDescribeCommand.cs

@@ -19,12 +19,6 @@ public class FirewallDescribeCommand(
 
         var sw = await useCase.ExecuteAsync(settings.Name);
 
-        if (sw == null)
-        {
-            AnsiConsole.MarkupLine($"[red]Firewall '{settings.Name}' not found.[/]");
-            return 1;
-        }
-
         var grid = new Grid()
             .AddColumn(new GridColumn().NoWrap())
             .AddColumn(new GridColumn().NoWrap());

+ 0 - 6
RackPeek/Commands/Firewalls/FirewallGetByNameCommand.cs

@@ -19,12 +19,6 @@ public class FirewallGetByNameCommand(
 
         var sw = await useCase.ExecuteAsync(settings.Name);
 
-        if (sw == null)
-        {
-            AnsiConsole.MarkupLine($"[red]Firewall '{settings.Name}' not found.[/]");
-            return 1;
-        }
-
         AnsiConsole.MarkupLine(
             $"[green]{sw.Name}[/]  Model: {sw.Model ?? "Unknown"}, Managed: {(sw.Managed == true ? "Yes" : "No")}, PoE: {(sw.Poe == true ? "Yes" : "No")}");
 

+ 1 - 1
RackPeek/Commands/Firewalls/FirewallGetCommand.cs

@@ -20,7 +20,7 @@ public class FirewallGetCommand(
 
         if (report.Firewalls.Count == 0)
         {
-            AnsiConsole.MarkupLine("[yellow]No Firewalles found.[/]");
+            AnsiConsole.MarkupLine("[yellow]No Firewalls found.[/]");
             return 0;
         }
 

+ 1 - 1
RackPeek/Commands/Firewalls/FirewallSetCommand.cs

@@ -33,7 +33,7 @@ public class FirewallSetCommand(
             settings.Managed,
             settings.Poe);
 
-        AnsiConsole.MarkupLine($"[green]Server '{settings.Name}' updated.[/]");
+        AnsiConsole.MarkupLine($"[green]Firewall '{settings.Name}' updated.[/]");
         return 0;
     }
 }

Деякі файли не було показано, через те що забагато файлів було змінено