Explorar el Código

Merge pull request #130 from Timmoth/usecase-consolidation

V1.0.0 Refactor / Prep
Tim Jones hace 1 mes
padre
commit
043b4355d1
Se han modificado 100 ficheros con 321 adiciones y 2083 borrados
  1. BIN
      .DS_Store
  2. 1 1
      .github/workflows/publish-cli.yml
  3. 1 3
      .github/workflows/publish-webui.yml
  4. 1 1
      .github/workflows/test-cli.yml
  5. 46 0
      .github/workflows/test-webui.yml
  6. 1 1
      .gitignore
  7. 3 4
      RackPeek.Domain/Helpers/DeepClone.cs
  8. 2 2
      RackPeek.Domain/Helpers/Normalize.cs
  9. 1 1
      RackPeek.Domain/Helpers/ThrowIfInvalid.cs
  10. 0 44
      RackPeek.Domain/Persistence/HardwareRepository.cs
  11. 6 2
      RackPeek.Domain/Persistence/IResourceCollection.cs
  12. 75 19
      RackPeek.Domain/Persistence/InMemoryResourceCollection.cs
  13. 9 9
      RackPeek.Domain/Persistence/ServiceRepository.cs
  14. 13 13
      RackPeek.Domain/Persistence/SystemRepository.cs
  15. 1 1
      RackPeek.Domain/Persistence/Yaml/Converters.cs
  16. 10 5
      RackPeek.Domain/Persistence/Yaml/ITextFileStore.cs
  17. 7 5
      RackPeek.Domain/Persistence/Yaml/NotesStringYamlConverter.cs
  18. 77 37
      RackPeek.Domain/Persistence/Yaml/YamlResourceCollection.cs
  19. 0 39
      RackPeek.Domain/Persistence/YamlResourceRepository.cs
  20. 0 52
      RackPeek.Domain/Persistence/YamlServerRepository.cs
  21. 1 1
      RackPeek.Domain/RackPeek.Domain.csproj
  22. 2 2
      RackPeek.Domain/Resources/AccessPoints/AccessPoint.cs
  23. 4 6
      RackPeek.Domain/Resources/AccessPoints/AccessPointHardwareReport.cs
  24. 4 7
      RackPeek.Domain/Resources/AccessPoints/UpdateAccessPointUseCase.cs
  25. 3 3
      RackPeek.Domain/Resources/Desktops/DescribeDesktopUseCase.cs
  26. 8 5
      RackPeek.Domain/Resources/Desktops/Desktop.cs
  27. 4 5
      RackPeek.Domain/Resources/Desktops/DesktopHardwareReport.cs
  28. 6 7
      RackPeek.Domain/Resources/Desktops/UpdateDesktopUseCase.cs
  29. 4 3
      RackPeek.Domain/Resources/Firewalls/DescribeFirewallUseCase.cs
  30. 5 2
      RackPeek.Domain/Resources/Firewalls/Firewall.cs
  31. 4 5
      RackPeek.Domain/Resources/Firewalls/FirewallHardwareReport.cs
  32. 4 7
      RackPeek.Domain/Resources/Firewalls/UpdateFirewallUseCase.cs
  33. 0 31
      RackPeek.Domain/Resources/Hardware/AccessPoints/CloneAccessPointUseCase.cs
  34. 0 27
      RackPeek.Domain/Resources/Hardware/AccessPoints/DeleteAccessPointUseCase.cs
  35. 0 28
      RackPeek.Domain/Resources/Hardware/AccessPoints/DescribeAccessPointUseCase.cs
  36. 0 17
      RackPeek.Domain/Resources/Hardware/AccessPoints/GetAccessPointUseCase.cs
  37. 0 12
      RackPeek.Domain/Resources/Hardware/AccessPoints/GetAccessPointsUseCase.cs
  38. 0 37
      RackPeek.Domain/Resources/Hardware/AccessPoints/RenameAccessPointUseCase.cs
  39. 0 29
      RackPeek.Domain/Resources/Hardware/Desktops/AddDesktopUseCase.cs
  40. 0 31
      RackPeek.Domain/Resources/Hardware/Desktops/CloneDesktopUsecase.cs
  41. 0 30
      RackPeek.Domain/Resources/Hardware/Desktops/Cpus/AddDesktopCpuUseCase.cs
  42. 0 23
      RackPeek.Domain/Resources/Hardware/Desktops/Cpus/RemoveDesktopCpuUseCase.cs
  43. 0 34
      RackPeek.Domain/Resources/Hardware/Desktops/Cpus/UpdateDesktopCpuUseCase.cs
  44. 0 27
      RackPeek.Domain/Resources/Hardware/Desktops/DeleteDesktopUseCase.cs
  45. 0 31
      RackPeek.Domain/Resources/Hardware/Desktops/Drives/AddDesktopDriveUseCase.cs
  46. 0 23
      RackPeek.Domain/Resources/Hardware/Desktops/Drives/RemoveDesktopDriveUseCase.cs
  47. 0 28
      RackPeek.Domain/Resources/Hardware/Desktops/Drives/UpdateDesktopDriveUseCase.cs
  48. 0 18
      RackPeek.Domain/Resources/Hardware/Desktops/GetDesktopUseCase.cs
  49. 0 12
      RackPeek.Domain/Resources/Hardware/Desktops/GetDesktopsUseCase.cs
  50. 0 30
      RackPeek.Domain/Resources/Hardware/Desktops/Gpus/AddDesktopGpuUseCase.cs
  51. 0 23
      RackPeek.Domain/Resources/Hardware/Desktops/Gpus/RemoveDesktopGpuUseCase.cs
  52. 0 31
      RackPeek.Domain/Resources/Hardware/Desktops/Gpus/UpdateDesktopGpuUseCase.cs
  53. 0 35
      RackPeek.Domain/Resources/Hardware/Desktops/Nics/AddDesktopNicUseCase.cs
  54. 0 23
      RackPeek.Domain/Resources/Hardware/Desktops/Nics/RemoveDesktopNicUseCase.cs
  55. 0 37
      RackPeek.Domain/Resources/Hardware/Desktops/Nics/UpdateDesktopNicUseCase.cs
  56. 0 37
      RackPeek.Domain/Resources/Hardware/Desktops/RenameDesktopUseCase.cs
  57. 0 24
      RackPeek.Domain/Resources/Hardware/Firewalls/AddFirewallUseCase.cs
  58. 0 31
      RackPeek.Domain/Resources/Hardware/Firewalls/CloneFirewallUseCase.cs
  59. 0 27
      RackPeek.Domain/Resources/Hardware/Firewalls/DeleteFirewallUseCase.cs
  60. 0 18
      RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallUseCase.cs
  61. 0 12
      RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallsUseCase.cs
  62. 0 35
      RackPeek.Domain/Resources/Hardware/Firewalls/Ports/AddFirewallPortUseCase.cs
  63. 0 23
      RackPeek.Domain/Resources/Hardware/Firewalls/Ports/RemoveFirewallPortUseCase.cs
  64. 0 37
      RackPeek.Domain/Resources/Hardware/Firewalls/Ports/UpdateFirewallPortUseCase.cs
  65. 0 37
      RackPeek.Domain/Resources/Hardware/Firewalls/RenameFirewallUseCase.cs
  66. 11 11
      RackPeek.Domain/Resources/Hardware/GetHardwareSystemTreeUseCase.cs
  67. 5 0
      RackPeek.Domain/Resources/Hardware/Hardware.cs
  68. 2 5
      RackPeek.Domain/Resources/Hardware/IHardwareRepository.cs
  69. 0 28
      RackPeek.Domain/Resources/Hardware/Laptops/AddLaptopUseCase.cs
  70. 0 31
      RackPeek.Domain/Resources/Hardware/Laptops/CloneLaptopUseCase.cs
  71. 0 23
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/AddDesktopCpuUseCase.cs
  72. 0 23
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/RemoveDesktopCpuUseCase.cs
  73. 0 32
      RackPeek.Domain/Resources/Hardware/Laptops/Cpus/UpdateDesktopCpuUseCase.cs
  74. 0 27
      RackPeek.Domain/Resources/Hardware/Laptops/DeleteLaptopUseCase.cs
  75. 0 30
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/AddDesktopDriveUseCase.cs
  76. 0 22
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/RemoveDesktopDriveUseCase.cs
  77. 0 28
      RackPeek.Domain/Resources/Hardware/Laptops/Drives/UpdateDesktopDriveUseCase.cs
  78. 0 18
      RackPeek.Domain/Resources/Hardware/Laptops/GetDesktopUseCase.cs
  79. 0 12
      RackPeek.Domain/Resources/Hardware/Laptops/GetLaptopsUseCase.cs
  80. 0 29
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/AddDesktopGpuUseCase.cs
  81. 0 22
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/RemoveDesktopGpuUseCase.cs
  82. 0 31
      RackPeek.Domain/Resources/Hardware/Laptops/Gpus/UpdateDesktopGpuUseCase.cs
  83. 0 37
      RackPeek.Domain/Resources/Hardware/Laptops/RenameLaptopUseCase.cs
  84. 0 24
      RackPeek.Domain/Resources/Hardware/Routers/AddRouterUseCase.cs
  85. 0 31
      RackPeek.Domain/Resources/Hardware/Routers/CloneRouterUseCase.cs
  86. 0 27
      RackPeek.Domain/Resources/Hardware/Routers/DeleteRouterUseCase.cs
  87. 0 18
      RackPeek.Domain/Resources/Hardware/Routers/GetRouterUseCase.cs
  88. 0 12
      RackPeek.Domain/Resources/Hardware/Routers/GetRoutersUseCase.cs
  89. 0 35
      RackPeek.Domain/Resources/Hardware/Routers/Ports/AddRouterPortUseCase.cs
  90. 0 23
      RackPeek.Domain/Resources/Hardware/Routers/Ports/RemoveRouterPortUseCase.cs
  91. 0 37
      RackPeek.Domain/Resources/Hardware/Routers/Ports/UpdateRouterPortUseCase.cs
  92. 0 37
      RackPeek.Domain/Resources/Hardware/Routers/RenameRouterUseCase.cs
  93. 0 24
      RackPeek.Domain/Resources/Hardware/Servers/AddServerUseCase.cs
  94. 0 31
      RackPeek.Domain/Resources/Hardware/Servers/CloneServerUseCase.cs
  95. 0 35
      RackPeek.Domain/Resources/Hardware/Servers/Cpus/AddCpuUseCase.cs
  96. 0 27
      RackPeek.Domain/Resources/Hardware/Servers/Cpus/RemoveCpuUseCase.cs
  97. 0 37
      RackPeek.Domain/Resources/Hardware/Servers/Cpus/UpdateCpuUseCase.cs
  98. 0 28
      RackPeek.Domain/Resources/Hardware/Servers/DeleteServerUseCase.cs
  99. 0 34
      RackPeek.Domain/Resources/Hardware/Servers/Drives/AddDriveUseCase.cs
  100. 0 24
      RackPeek.Domain/Resources/Hardware/Servers/Drives/RemoveDriveUseCase.cs

BIN
.DS_Store


+ 1 - 1
.github/workflows/publish.yml → .github/workflows/publish-cli.yml

@@ -1,4 +1,4 @@
-name: RackPeek Release
+name: CLI Publish
 
 on:
   workflow_dispatch:

+ 1 - 3
.github/workflows/publish-demo.yml → .github/workflows/publish-webui.yml

@@ -1,8 +1,6 @@
-name: Deploy Blazor WASM to GitHub Pages
+name: WebUi Publish
 
 on:
-  push:
-    branches: [ "main" ]
   workflow_dispatch:
 
 permissions:

+ 1 - 1
.github/workflows/main.yml → .github/workflows/test-cli.yml

@@ -1,4 +1,4 @@
-name: Test
+name: CLI Tests
 
 on:
   pull_request:

+ 46 - 0
.github/workflows/test-webui.yml

@@ -0,0 +1,46 @@
+name: WebUi Tests
+
+on:
+  pull_request:
+  workflow_dispatch:
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Setup .NET
+        uses: actions/setup-dotnet@v3
+        with:
+          dotnet-version: 10.0.x
+
+      # Restore all projects
+      - name: Restore
+        run: dotnet restore
+
+      # Build solution
+      - name: Build
+        run: dotnet build --no-restore --configuration Release
+
+      # Install Playwright CLI
+      - name: Install Playwright CLI
+        run: dotnet tool install --global Microsoft.Playwright.CLI
+
+      # Install browser binaries + Linux deps
+      - name: Install Playwright Browsers
+        run: |
+          playwright install --with-deps
+
+      # Build Docker image used by Testcontainers
+      - name: Build Docker Image
+        run: |
+          docker build \
+            -t rackpeek:ci \
+            -f RackPeek.Web/Dockerfile \
+            .
+
+      # Run E2E tests
+      - name: Run E2E Tests
+        run: dotnet test Tests.E2e --configuration Release --verbosity normal

+ 1 - 1
.gitignore

@@ -134,7 +134,7 @@ ipch/
 *.sap
 
 # Visual Studio Trace Files
-*.e2e
+# *.e2e
 
 # TFS 2012 Local Workspace
 $tf/

+ 3 - 4
RackPeek.Domain/Helpers/DeepClone.cs

@@ -1,7 +1,7 @@
-namespace RackPeek.Domain.Helpers;
-
 using System.Text.Json;
 
+namespace RackPeek.Domain.Helpers;
+
 public static class Clone
 {
     public static T DeepClone<T>(T obj)
@@ -9,5 +9,4 @@ public static class Clone
         var json = JsonSerializer.Serialize(obj);
         return JsonSerializer.Deserialize<T>(json)!;
     }
-
-}
+}

+ 2 - 2
RackPeek.Domain/Helpers/Normalize.cs

@@ -31,12 +31,12 @@ public static class Normalize
     {
         return name.Trim();
     }
-    
+
     public static string ResourceName(string name)
     {
         return name.Trim();
     }
-    
+
     public static string Tag(string name)
     {
         return name.Trim();

+ 1 - 1
RackPeek.Domain/Helpers/ThrowIfInvalid.cs

@@ -1,5 +1,5 @@
 using System.ComponentModel.DataAnnotations;
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Resources.SubResources;
 using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Helpers;

+ 0 - 44
RackPeek.Domain/Persistence/YamlHardwareRepository.cs → RackPeek.Domain/Persistence/HardwareRepository.cs

@@ -1,5 +1,4 @@
 using RackPeek.Domain.Resources.Hardware;
-using RackPeek.Domain.Resources.Models;
 
 namespace RackPeek.Domain.Persistence;
 
@@ -17,16 +16,6 @@ public class YamlHardwareRepository(IResourceCollection resources) : IHardwareRe
             .ToDictionary(k => k.Key, v => v.Count()));
     }
 
-    public Task<IReadOnlyList<Hardware>> GetAllAsync()
-    {
-        return Task.FromResult(resources.HardwareResources);
-    }
-
-    public Task<Hardware?> GetByNameAsync(string name)
-    {
-        return Task.FromResult(resources.GetByName(name) as Hardware);
-    }
-
     public Task<List<HardwareTree>> GetTreeAsync()
     {
         var hardwareTree = new List<HardwareTree>();
@@ -72,37 +61,4 @@ public class YamlHardwareRepository(IResourceCollection resources) : IHardwareRe
 
         return Task.FromResult(hardwareTree);
     }
-
-
-    public async Task AddAsync(Hardware hardware)
-    {
-        if (resources.HardwareResources.Any(r =>
-                r.Name.Equals(hardware.Name, StringComparison.OrdinalIgnoreCase)))
-            throw new InvalidOperationException(
-                $"Hardware with name '{hardware.Name}' already exists.");
-
-        await resources.AddAsync(hardware);
-    }
-
-    public async Task UpdateAsync(Hardware hardware)
-    {
-        var existing = resources.HardwareResources
-            .FirstOrDefault(r => r.Name.Equals(hardware.Name, StringComparison.OrdinalIgnoreCase));
-
-        if (existing == null)
-            throw new InvalidOperationException($"Hardware '{hardware.Name}' not found.");
-
-        await resources.UpdateAsync(hardware);
-    }
-
-    public async Task DeleteAsync(string name)
-    {
-        var existing = resources.HardwareResources
-            .FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
-
-        if (existing == null)
-            throw new InvalidOperationException($"Hardware '{name}' not found.");
-
-        await resources.DeleteAsync(name);
-    }
 }

+ 6 - 2
RackPeek.Domain/Persistence/IResourceCollection.cs

@@ -1,5 +1,5 @@
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Resources.Hardware;
 using RackPeek.Domain.Resources.Services;
 using RackPeek.Domain.Resources.SystemResources;
 
@@ -14,12 +14,16 @@ public interface IResourceCollection
     Task AddAsync(Resource resource);
     Task UpdateAsync(Resource resource);
     Task DeleteAsync(string name);
+    Task<Resource?> GetByNameAsync(string name);
+    Task<T?> GetByNameAsync<T>(string name) where T : Resource;
 
     Resource? GetByName(string name);
     Task<bool> Exists(string name);
 
-    Task LoadAsync();   // required for WASM startup
+    Task LoadAsync(); // required for WASM startup
     Task<IReadOnlyList<Resource>> GetByTagAsync(string name);
     public Task<Dictionary<string, int>> GetTagsAsync();
 
+    Task<IReadOnlyList<T>> GetAllOfTypeAsync<T>();
+    Task<IReadOnlyList<Resource>> GetDependantsAsync(string name);
 }

+ 75 - 19
RackPeek.Domain/Persistence/InMemoryResourceCollection.cs

@@ -1,7 +1,15 @@
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Resources.AccessPoints;
+using RackPeek.Domain.Resources.Desktops;
+using RackPeek.Domain.Resources.Firewalls;
+using RackPeek.Domain.Resources.Hardware;
+using RackPeek.Domain.Resources.Laptops;
+using RackPeek.Domain.Resources.Routers;
+using RackPeek.Domain.Resources.Servers;
 using RackPeek.Domain.Resources.Services;
+using RackPeek.Domain.Resources.Switches;
 using RackPeek.Domain.Resources.SystemResources;
+using RackPeek.Domain.Resources.UpsUnits;
 
 namespace RackPeek.Domain.Persistence;
 
@@ -15,7 +23,9 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         get
         {
             lock (_lock)
+            {
                 return _resources.OfType<Hardware>().ToList();
+            }
         }
     }
 
@@ -24,7 +34,9 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         get
         {
             lock (_lock)
+            {
                 return _resources.OfType<SystemResource>().ToList();
+            }
         }
     }
 
@@ -33,7 +45,9 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         get
         {
             lock (_lock)
+            {
                 return _resources.OfType<Service>().ToList();
+            }
         }
     }
 
@@ -47,12 +61,16 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
     }
 
     public Task LoadAsync()
-        => Task.CompletedTask;
+    {
+        return Task.CompletedTask;
+    }
 
     public Task<IReadOnlyList<Resource>> GetByTagAsync(string name)
     {
         lock (_lock)
+        {
             return Task.FromResult<IReadOnlyList<Resource>>(_resources.Where(r => r.Tags.Contains(name)).ToList());
+        }
     }
 
     public Task<Dictionary<string, int>> GetTagsAsync()
@@ -61,7 +79,7 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         {
             var result = _resources
                 .Where(r => r.Tags != null)
-                .SelectMany(r => r.Tags!)      // flatten all tag arrays
+                .SelectMany(r => r.Tags!) // flatten all tag arrays
                 .Where(t => !string.IsNullOrWhiteSpace(t))
                 .GroupBy(t => t)
                 .ToDictionary(g => g.Key, g => g.Count());
@@ -69,6 +87,23 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         }
     }
 
+    public Task<IReadOnlyList<T>> GetAllOfTypeAsync<T>()
+    {
+        lock (_lock)
+        {
+            return Task.FromResult<IReadOnlyList<T>>(_resources.OfType<T>().ToList());
+        }
+    }
+
+    public Task<IReadOnlyList<Resource>> GetDependantsAsync(string name)
+    {
+        lock (_lock)
+        {
+            return Task.FromResult<IReadOnlyList<Resource>>(_resources
+                .Where(r => r.RunsOn?.Equals(name, StringComparison.OrdinalIgnoreCase) ?? false).ToList());
+        }
+    }
+
 
     public Task AddAsync(Resource resource)
     {
@@ -113,6 +148,24 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         return Task.CompletedTask;
     }
 
+    public Task<Resource?> GetByNameAsync(string name)
+    {
+        lock (_lock)
+        {
+            return Task.FromResult(_resources.FirstOrDefault(r =>
+                r.Name.Equals(name, StringComparison.OrdinalIgnoreCase)));
+        }
+    }
+
+    public Task<T?> GetByNameAsync<T>(string name) where T : Resource
+    {
+        lock (_lock)
+        {
+            var resource = _resources.FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
+            return Task.FromResult<T?>(resource as T);
+        }
+    }
+
     public Resource? GetByName(string name)
     {
         lock (_lock)
@@ -122,19 +175,22 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         }
     }
 
-    private static string GetKind(Resource resource) => resource switch
-    {
-        Server => "Server",
-        Switch => "Switch",
-        Firewall => "Firewall",
-        Router => "Router",
-        Desktop => "Desktop",
-        Laptop => "Laptop",
-        AccessPoint => "AccessPoint",
-        Ups => "Ups",
-        SystemResource => "System",
-        Service => "Service",
-        _ => throw new InvalidOperationException(
-            $"Unknown resource type: {resource.GetType().Name}")
-    };
-}
+    private static string GetKind(Resource resource)
+    {
+        return resource switch
+        {
+            Server => "Server",
+            Switch => "Switch",
+            Firewall => "Firewall",
+            Router => "Router",
+            Desktop => "Desktop",
+            Laptop => "Laptop",
+            AccessPoint => "AccessPoint",
+            Ups => "Ups",
+            SystemResource => "System",
+            Service => "Service",
+            _ => throw new InvalidOperationException(
+                $"Unknown resource type: {resource.GetType().Name}")
+        };
+    }
+}

+ 9 - 9
RackPeek.Domain/Persistence/YamlServiceRepository.cs → RackPeek.Domain/Persistence/ServiceRepository.cs

@@ -2,7 +2,7 @@ using RackPeek.Domain.Resources.Services;
 
 namespace RackPeek.Domain.Persistence;
 
-public class YamlServiceRepository(IResourceCollection resources) : IServiceRepository
+public class ServiceRepository(IResourceCollection resources) : IServiceRepository
 {
     public Task<int> GetCountAsync()
     {
@@ -18,6 +18,14 @@ public class YamlServiceRepository(IResourceCollection resources) : IServiceRepo
             .Count());
     }
 
+    public Task<IReadOnlyList<Service>> GetBySystemHostAsync(string systemHostName)
+    {
+        var systemHostNameLower = systemHostName.ToLower().Trim();
+        var results = resources.ServiceResources
+            .Where(s => s.RunsOn != null && s.RunsOn.ToLower().Equals(systemHostNameLower)).ToList();
+        return Task.FromResult<IReadOnlyList<Service>>(results);
+    }
+
     public Task<IReadOnlyList<Service>> GetAllAsync()
     {
         return Task.FromResult(resources.ServiceResources);
@@ -28,14 +36,6 @@ public class YamlServiceRepository(IResourceCollection resources) : IServiceRepo
         return Task.FromResult(resources.GetByName(name) as Service);
     }
 
-    public Task<IReadOnlyList<Service>> GetBySystemHostAsync(string systemHostName)
-    {
-        var systemHostNameLower = systemHostName.ToLower().Trim();
-        var results = resources.ServiceResources
-            .Where(s => s.RunsOn != null && s.RunsOn.ToLower().Equals(systemHostNameLower)).ToList();
-        return Task.FromResult<IReadOnlyList<Service>>(results);
-    }
-
     public async Task AddAsync(Service service)
     {
         if (resources.ServiceResources.Any(r =>

+ 13 - 13
RackPeek.Domain/Persistence/YamlSystemRepository.cs → RackPeek.Domain/Persistence/SystemRepository.cs

@@ -25,11 +25,6 @@ public class YamlSystemRepository(IResourceCollection resources) : ISystemReposi
             .ToDictionary(k => k.Key, v => v.Count()));
     }
 
-    public Task<IReadOnlyList<SystemResource>> GetAllAsync()
-    {
-        return Task.FromResult(resources.SystemResources);
-    }
-
     public Task<IReadOnlyList<SystemResource>> GetFilteredAsync(
         string? typeFilter,
         string? osFilter)
@@ -49,6 +44,19 @@ public class YamlSystemRepository(IResourceCollection resources) : ISystemReposi
         return Task.FromResult<IReadOnlyList<SystemResource>>(results);
     }
 
+    public Task<IReadOnlyList<SystemResource>> GetByPhysicalHostAsync(string physicalHostName)
+    {
+        var physicalHostNameLower = physicalHostName.ToLower().Trim();
+        var results = resources.SystemResources
+            .Where(s => s.RunsOn != null && s.RunsOn.ToLower().Equals(physicalHostNameLower)).ToList();
+        return Task.FromResult<IReadOnlyList<SystemResource>>(results);
+    }
+
+    public Task<IReadOnlyList<SystemResource>> GetAllAsync()
+    {
+        return Task.FromResult(resources.SystemResources);
+    }
+
     private static string? Normalize(string? value)
     {
         return string.IsNullOrWhiteSpace(value) ? null : value.Trim().ToLower();
@@ -60,14 +68,6 @@ public class YamlSystemRepository(IResourceCollection resources) : ISystemReposi
         return Task.FromResult(resources.GetByName(name) as SystemResource);
     }
 
-    public Task<IReadOnlyList<SystemResource>> GetByPhysicalHostAsync(string physicalHostName)
-    {
-        var physicalHostNameLower = physicalHostName.ToLower().Trim();
-        var results = resources.SystemResources
-            .Where(s => s.RunsOn != null && s.RunsOn.ToLower().Equals(physicalHostNameLower)).ToList();
-        return Task.FromResult<IReadOnlyList<SystemResource>>(results);
-    }
-
     public async Task AddAsync(SystemResource systemResource)
     {
         if (resources.SystemResources.Any(r =>

+ 1 - 1
RackPeek.Domain/Persistence/Yaml/Converters.cs

@@ -4,7 +4,7 @@ using YamlDotNet.Core;
 using YamlDotNet.Core.Events;
 using YamlDotNet.Serialization;
 
-namespace RackPeek.Yaml;
+namespace RackPeek.Domain.Persistence.Yaml;
 
 public static class StorageSizeParser
 {

+ 10 - 5
RackPeek.Domain/Persistence/Yaml/ITextFileStore.cs

@@ -7,15 +7,20 @@ public interface ITextFileStore
     Task WriteAllTextAsync(string path, string contents);
 }
 
-
 public sealed class PhysicalTextFileStore : ITextFileStore
 {
     public Task<bool> ExistsAsync(string path)
-        => Task.FromResult(File.Exists(path));
+    {
+        return Task.FromResult(File.Exists(path));
+    }
 
     public Task<string> ReadAllTextAsync(string path)
-        => File.ReadAllTextAsync(path);
+    {
+        return File.ReadAllTextAsync(path);
+    }
 
     public Task WriteAllTextAsync(string path, string contents)
-        => File.WriteAllTextAsync(path, contents);
-}
+    {
+        return File.WriteAllTextAsync(path, contents);
+    }
+}

+ 7 - 5
RackPeek.Domain/Persistence/Yaml/NotesStringYamlConverter.cs

@@ -2,9 +2,15 @@ using YamlDotNet.Core;
 using YamlDotNet.Core.Events;
 using YamlDotNet.Serialization;
 
+namespace RackPeek.Domain.Persistence.Yaml;
+
 public sealed class NotesStringYamlConverter : IYamlTypeConverter
 {
-    public bool Accepts(Type type) => type == typeof(string);
+    public bool Accepts(Type type)
+    {
+        return type == typeof(string);
+    }
+
     public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
     {
         var scalar = parser.Consume<Scalar>();
@@ -28,7 +34,6 @@ public sealed class NotesStringYamlConverter : IYamlTypeConverter
         var s = (string)value;
 
         if (s.Contains('\n'))
-        {
             // Literal block style (|)
             emitter.Emit(new Scalar(
                 AnchorName.Empty,
@@ -37,10 +42,7 @@ public sealed class NotesStringYamlConverter : IYamlTypeConverter
                 ScalarStyle.Literal,
                 true,
                 false));
-        }
         else
-        {
             emitter.Emit(new Scalar(s));
-        }
     }
 }

+ 77 - 37
RackPeek.Domain/Persistence/Yaml/YamlResourceCollection.cs

@@ -1,21 +1,28 @@
 using System.Collections.Specialized;
-using RackPeek.Domain.Persistence;
-using RackPeek.Domain.Persistence.Yaml;
 using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Resources.AccessPoints;
+using RackPeek.Domain.Resources.Desktops;
+using RackPeek.Domain.Resources.Firewalls;
+using RackPeek.Domain.Resources.Hardware;
+using RackPeek.Domain.Resources.Laptops;
+using RackPeek.Domain.Resources.Routers;
+using RackPeek.Domain.Resources.Servers;
 using RackPeek.Domain.Resources.Services;
+using RackPeek.Domain.Resources.Switches;
 using RackPeek.Domain.Resources.SystemResources;
-using RackPeek.Yaml;
+using RackPeek.Domain.Resources.UpsUnits;
 using YamlDotNet.Core;
 using YamlDotNet.Serialization;
 using YamlDotNet.Serialization.NamingConventions;
 
+namespace RackPeek.Domain.Persistence.Yaml;
+
 public class ResourceCollection
 {
-    public List<Resource> Resources { get; } = new();
     public readonly SemaphoreSlim FileLock = new(1, 1);
-
+    public List<Resource> Resources { get; } = new();
 }
+
 public sealed class YamlResourceCollection(
     string filePath,
     ITextFileStore fileStore,
@@ -27,24 +34,35 @@ public sealed class YamlResourceCollection(
         return Task.FromResult(resourceCollection.Resources.Exists(r =>
             r.Name.Equals(name, StringComparison.OrdinalIgnoreCase)));
     }
-    
+
     public Task<Dictionary<string, int>> GetTagsAsync()
     {
         var result = resourceCollection.Resources
             .Where(r => r.Tags != null)
-            .SelectMany(r => r.Tags!)      // flatten all tag arrays
+            .SelectMany(r => r.Tags!) // flatten all tag arrays
             .Where(t => !string.IsNullOrWhiteSpace(t))
             .GroupBy(t => t)
             .ToDictionary(g => g.Key, g => g.Count());
         return Task.FromResult(result);
     }
 
+    public Task<IReadOnlyList<T>> GetAllOfTypeAsync<T>()
+    {
+        return Task.FromResult<IReadOnlyList<T>>(resourceCollection.Resources.OfType<T>().ToList());
+    }
+
+    public Task<IReadOnlyList<Resource>> GetDependantsAsync(string name)
+    {
+        return Task.FromResult<IReadOnlyList<Resource>>(resourceCollection.Resources
+            .Where(r => r.RunsOn?.Equals(name, StringComparison.OrdinalIgnoreCase) ?? false).ToList());
+    }
 
     public Task<IReadOnlyList<Resource>> GetByTagAsync(string name)
     {
-        return Task.FromResult<IReadOnlyList<Resource>>(resourceCollection.Resources.Where(r => r.Tags.Contains(name)).ToList());
+        return Task.FromResult<IReadOnlyList<Resource>>(resourceCollection.Resources.Where(r => r.Tags.Contains(name))
+            .ToList());
     }
-    
+
     public async Task LoadAsync()
     {
         var loaded = await LoadFromFileAsync();
@@ -69,12 +87,28 @@ public sealed class YamlResourceCollection(
     public IReadOnlyList<Service> ServiceResources =>
         resourceCollection.Resources.OfType<Service>().ToList();
 
-    public Resource? GetByName(string name) =>
-        resourceCollection.Resources.FirstOrDefault(r =>
+    public Task<Resource?> GetByNameAsync(string name)
+    {
+        return Task.FromResult(resourceCollection.Resources.FirstOrDefault(r =>
+            r.Name.Equals(name, StringComparison.OrdinalIgnoreCase)));
+    }
+
+    public Task<T?> GetByNameAsync<T>(string name) where T : Resource
+    {
+        var resource =
+            resourceCollection.Resources.FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
+        return Task.FromResult<T?>(resource as T);
+    }
+
+    public Resource? GetByName(string name)
+    {
+        return resourceCollection.Resources.FirstOrDefault(r =>
             r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
+    }
 
-    public Task AddAsync(Resource resource) =>
-        UpdateWithLockAsync(list =>
+    public Task AddAsync(Resource resource)
+    {
+        return UpdateWithLockAsync(list =>
         {
             if (list.Any(r => r.Name.Equals(resource.Name, StringComparison.OrdinalIgnoreCase)))
                 throw new InvalidOperationException($"'{resource.Name}' already exists.");
@@ -82,9 +116,11 @@ public sealed class YamlResourceCollection(
             resource.Kind = GetKind(resource);
             list.Add(resource);
         });
+    }
 
-    public Task UpdateAsync(Resource resource) =>
-        UpdateWithLockAsync(list =>
+    public Task UpdateAsync(Resource resource)
+    {
+        return UpdateWithLockAsync(list =>
         {
             var index = list.FindIndex(r => r.Name.Equals(resource.Name, StringComparison.OrdinalIgnoreCase));
             if (index == -1) throw new InvalidOperationException("Not found.");
@@ -92,10 +128,13 @@ public sealed class YamlResourceCollection(
             resource.Kind = GetKind(resource);
             list[index] = resource;
         });
+    }
 
-    public Task DeleteAsync(string name) =>
-        UpdateWithLockAsync(list =>
+    public Task DeleteAsync(string name)
+    {
+        return UpdateWithLockAsync(list =>
             list.RemoveAll(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase)));
+    }
 
     private async Task UpdateWithLockAsync(Action<List<Resource>> action)
     {
@@ -133,14 +172,13 @@ public sealed class YamlResourceCollection(
     {
         var yaml = await fileStore.ReadAllTextAsync(filePath);
         if (string.IsNullOrWhiteSpace(yaml))
-            return new();
+            return new List<Resource>();
 
         var deserializer = new DeserializerBuilder()
             .WithNamingConvention(CamelCaseNamingConvention.Instance)
             .WithCaseInsensitivePropertyMatching()
             .WithTypeConverter(new StorageSizeYamlConverter())
             .WithTypeConverter(new NotesStringYamlConverter())
-            
             .WithTypeDiscriminatingNodeDeserializer(options =>
             {
                 options.AddKeyValueTypeDiscriminator<Resource>("kind", new Dictionary<string, Type>
@@ -162,28 +200,31 @@ public sealed class YamlResourceCollection(
         try
         {
             var root = deserializer.Deserialize<YamlRoot>(yaml);
-            return root?.Resources ?? new();
+            return root?.Resources ?? new List<Resource>();
         }
         catch (YamlException)
         {
-            return new();
+            return new List<Resource>();
         }
     }
 
-    private string GetKind(Resource resource) => resource switch
+    private string GetKind(Resource resource)
     {
-        Server => "Server",
-        Switch => "Switch",
-        Firewall => "Firewall",
-        Router => "Router",
-        Desktop => "Desktop",
-        Laptop => "Laptop",
-        AccessPoint => "AccessPoint",
-        Ups => "Ups",
-        SystemResource => "System",
-        Service => "Service",
-        _ => throw new InvalidOperationException($"Unknown resource type: {resource.GetType().Name}")
-    };
+        return resource switch
+        {
+            Server => "Server",
+            Switch => "Switch",
+            Firewall => "Firewall",
+            Router => "Router",
+            Desktop => "Desktop",
+            Laptop => "Laptop",
+            AccessPoint => "AccessPoint",
+            Ups => "Ups",
+            SystemResource => "System",
+            Service => "Service",
+            _ => throw new InvalidOperationException($"Unknown resource type: {resource.GetType().Name}")
+        };
+    }
 
     private OrderedDictionary SerializeResource(Resource resource)
     {
@@ -213,9 +254,8 @@ public sealed class YamlResourceCollection(
 
         return map;
     }
-
-
 }
+
 public class YamlRoot
 {
     public List<Resource>? Resources { get; set; }

+ 0 - 39
RackPeek.Domain/Persistence/YamlResourceRepository.cs

@@ -1,39 +0,0 @@
-using RackPeek.Domain.Resources;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.Services;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Persistence;
-
-public class YamlResourceRepository(IResourceCollection resources) : IResourceRepository
-{
-    public Task<string?> GetResourceKindAsync(string name)
-    {
-        // Use the centralized GetByName which handles casing correctly
-        var resource = resources.GetByName(name);
-
-        // Return the Kind label if it exists
-        return Task.FromResult(resource switch
-        {
-            Hardware h => h.Kind,
-            SystemResource s => SystemResource.KindLabel,
-            Service svc => Service.KindLabel,
-            _ => null
-        });
-    }
-
-    public Task<bool> ResourceExistsAsync(string name)
-    {
-        return Task.FromResult(resources.GetByName(name) != null);
-    }
-
-    public Task<IReadOnlyList<Resource>> GetByTagAsync(string name)
-    {
-        return resources.GetByTagAsync(name);
-    }
-
-    public Task<Dictionary<string, int>> GetTagsAsync()
-    {
-        return resources.GetTagsAsync();
-    }
-}

+ 0 - 52
RackPeek.Domain/Persistence/YamlServerRepository.cs

@@ -1,52 +0,0 @@
-using RackPeek.Domain.Resources.Hardware;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.Services;
-
-namespace RackPeek.Domain.Persistence;
-
-public class YamlHardwareRepo<T>(IResourceCollection resources) : IResourceRepo<T> where T : Hardware
-{
-    public Task<IReadOnlyList<T>> GetAllAsync()
-    {
-        var servers = resources.HardwareResources.OfType<T>().ToList();
-        return Task.FromResult<IReadOnlyList<T>>(servers.AsReadOnly());
-    }
-
-    public async Task AddAsync(T service)
-    {
-        if (await resources.Exists(service.Name))
-            throw new InvalidOperationException(
-                $"Resource with name '{service.Name}' already exists.");
-
-        await resources.AddAsync(service);
-    }
-    
-    public async Task UpdateAsync(T service)
-    {
-        var existing = resources.HardwareResources
-            .FirstOrDefault(r => r.Name.Equals(service.Name, StringComparison.OrdinalIgnoreCase));
-        
-        if (existing is not T)
-            throw new InvalidOperationException($"'{service.Name}' not found.");
-
-        await resources.UpdateAsync(service);
-        
-    }
-
-    public async Task DeleteAsync(string name)
-    {
-        var existing = resources.HardwareResources
-            .FirstOrDefault(r => r.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
-
-        if (existing is not Server)
-            throw new InvalidOperationException($"'{name}' not found.");
-
-        await resources.DeleteAsync(name);
-        
-    }
-
-    public Task<T?> GetByNameAsync(string name)
-    {
-        return Task.FromResult(resources.GetByName(name) as T);
-    }
-}

+ 1 - 1
RackPeek.Domain/RackPeek.Domain.csproj

@@ -8,7 +8,7 @@
 
     <ItemGroup>
         <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2"/>
-        <PackageReference Include="YamlDotNet" Version="16.3.0" />
+        <PackageReference Include="YamlDotNet" Version="16.3.0"/>
     </ItemGroup>
 
 </Project>

+ 2 - 2
RackPeek.Domain/Resources/Models/AccessPoint.cs → RackPeek.Domain/Resources/AccessPoints/AccessPoint.cs

@@ -1,6 +1,6 @@
-namespace RackPeek.Domain.Resources.Models;
+namespace RackPeek.Domain.Resources.AccessPoints;
 
-public class AccessPoint : Hardware
+public class AccessPoint : Hardware.Hardware
 {
     public const string KindLabel = "AccessPoint";
     public string? Model { get; set; }

+ 4 - 6
RackPeek.Domain/Resources/Hardware/AccessPoints/AccessPointHardwareReport.cs → RackPeek.Domain/Resources/AccessPoints/AccessPointHardwareReport.cs

@@ -1,6 +1,6 @@
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Persistence;
 
-namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
+namespace RackPeek.Domain.Resources.AccessPoints;
 
 public record AccessPointHardwareReport(
     IReadOnlyList<AccessPointHardwareRow> AccessPoints
@@ -12,13 +12,11 @@ public record AccessPointHardwareRow(
     double SpeedGb
 );
 
-public class AccessPointHardwareReportUseCase(IHardwareRepository repository) : IUseCase
+public class AccessPointHardwareReportUseCase(IResourceCollection repository) : IUseCase
 {
     public async Task<AccessPointHardwareReport> ExecuteAsync()
     {
-        var hardware = await repository.GetAllAsync();
-        var aps = hardware.OfType<AccessPoint>();
-
+        var aps = await repository.GetAllOfTypeAsync<AccessPoint>();
         var rows = aps.Select(ap => new AccessPointHardwareRow(
             ap.Name,
             ap.Model ?? "Unknown",

+ 4 - 7
RackPeek.Domain/Resources/Hardware/AccessPoints/UpdateAccessPointUseCase.cs → RackPeek.Domain/Resources/AccessPoints/UpdateAccessPointUseCase.cs

@@ -1,9 +1,9 @@
 using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Persistence;
 
-namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
+namespace RackPeek.Domain.Resources.AccessPoints;
 
-public class UpdateAccessPointUseCase(IHardwareRepository repository) : IUseCase
+public class UpdateAccessPointUseCase(IResourceCollection repository) : IUseCase
 {
     public async Task ExecuteAsync(
         string name,
@@ -32,10 +32,7 @@ public class UpdateAccessPointUseCase(IHardwareRepository repository) : IUseCase
             ap.Speed = speed.Value;
         }
 
-        if (notes != null)
-        {
-            ap.Notes = notes;
-        }
+        if (notes != null) ap.Notes = notes;
 
         await repository.UpdateAsync(ap);
     }

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

@@ -1,7 +1,7 @@
 using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Persistence;
 
-namespace RackPeek.Domain.Resources.Hardware.Desktops;
+namespace RackPeek.Domain.Resources.Desktops;
 
 public record DesktopDescription(
     string Name,
@@ -13,7 +13,7 @@ public record DesktopDescription(
     int GpuCount
 );
 
-public class DescribeDesktopUseCase(IHardwareRepository repository) : IUseCase
+public class DescribeDesktopUseCase(IResourceCollection repository) : IUseCase
 {
     public async Task<DesktopDescription> ExecuteAsync(string name)
     {

+ 8 - 5
RackPeek.Domain/Resources/Models/Desktop.cs → RackPeek.Domain/Resources/Desktops/Desktop.cs

@@ -1,12 +1,15 @@
-namespace RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Resources.Servers;
+using RackPeek.Domain.Resources.SubResources;
 
-public class Desktop : Hardware
+namespace RackPeek.Domain.Resources.Desktops;
+
+public class Desktop : Hardware.Hardware, ICpuResource, IDriveResource, IGpuResource, INicResource
 {
     public const string KindLabel = "Desktop";
-    public List<Cpu>? Cpus { get; set; }
     public Ram? Ram { get; set; }
+    public string Model { get; set; }
+    public List<Cpu>? Cpus { get; set; }
     public List<Drive>? Drives { get; set; }
-    public List<Nic>? Nics { get; set; }
     public List<Gpu>? Gpus { get; set; }
-    public string Model { get; set; }
+    public List<Nic>? Nics { get; set; }
 }

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

@@ -1,6 +1,6 @@
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Persistence;
 
-namespace RackPeek.Domain.Resources.Hardware.Desktops;
+namespace RackPeek.Domain.Resources.Desktops;
 
 public record DesktopHardwareReport(
     IReadOnlyList<DesktopHardwareRow> Desktops
@@ -19,12 +19,11 @@ public record DesktopHardwareRow(
     string GpuSummary
 );
 
-public class DesktopHardwareReportUseCase(IHardwareRepository repository) : IUseCase
+public class DesktopHardwareReportUseCase(IResourceCollection repository) : IUseCase
 {
     public async Task<DesktopHardwareReport> ExecuteAsync()
     {
-        var hardware = await repository.GetAllAsync();
-        var desktops = hardware.OfType<Desktop>();
+        var desktops = await repository.GetAllOfTypeAsync<Desktop>();
 
         var rows = desktops.Select(desktop =>
         {

+ 6 - 7
RackPeek.Domain/Resources/Hardware/Desktops/UpdateDesktopUseCase.cs → RackPeek.Domain/Resources/Desktops/UpdateDesktopUseCase.cs

@@ -1,9 +1,10 @@
 using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Persistence;
+using RackPeek.Domain.Resources.SubResources;
 
-namespace RackPeek.Domain.Resources.Hardware.Desktops;
+namespace RackPeek.Domain.Resources.Desktops;
 
-public class UpdateDesktopUseCase(IHardwareRepository repository) : IUseCase
+public class UpdateDesktopUseCase(IResourceCollection repository) : IUseCase
 {
     public async Task ExecuteAsync(
         string name,
@@ -38,10 +39,8 @@ public class UpdateDesktopUseCase(IHardwareRepository repository) : IUseCase
             desktop.Ram ??= new Ram();
             desktop.Ram.Mts = ramMts.Value;
         }
-        if (notes != null)
-        {
-            desktop.Notes = notes;
-        }
+
+        if (notes != null) desktop.Notes = notes;
         await repository.UpdateAsync(desktop);
     }
 }

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

@@ -1,7 +1,8 @@
 using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Persistence;
+using RackPeek.Domain.Resources.SubResources;
 
-namespace RackPeek.Domain.Resources.Hardware.Firewalls;
+namespace RackPeek.Domain.Resources.Firewalls;
 
 public record FirewallDescription(
     string Name,
@@ -13,7 +14,7 @@ public record FirewallDescription(
     string PortSummary
 );
 
-public class DescribeFirewallUseCase(IHardwareRepository repository) : IUseCase
+public class DescribeFirewallUseCase(IResourceCollection repository) : IUseCase
 {
     public async Task<FirewallDescription> ExecuteAsync(string name)
     {

+ 5 - 2
RackPeek.Domain/Resources/Models/Firewall.cs → RackPeek.Domain/Resources/Firewalls/Firewall.cs

@@ -1,6 +1,9 @@
-namespace RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Resources.Servers;
+using RackPeek.Domain.Resources.SubResources;
 
-public class Firewall : Hardware
+namespace RackPeek.Domain.Resources.Firewalls;
+
+public class Firewall : Hardware.Hardware, IPortResource
 {
     public const string KindLabel = "Firewall";
     public string? Model { get; set; }

+ 4 - 5
RackPeek.Domain/Resources/Hardware/Firewalls/FirewallHardwareReport.cs → RackPeek.Domain/Resources/Firewalls/FirewallHardwareReport.cs

@@ -1,6 +1,6 @@
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Persistence;
 
-namespace RackPeek.Domain.Resources.Hardware.Firewalls;
+namespace RackPeek.Domain.Resources.Firewalls;
 
 public record FirewallHardwareReport(
     IReadOnlyList<FirewallHardwareRow> Firewalls
@@ -16,12 +16,11 @@ public record FirewallHardwareRow(
     string PortSummary
 );
 
-public class FirewallHardwareReportUseCase(IHardwareRepository repository) : IUseCase
+public class FirewallHardwareReportUseCase(IResourceCollection repository) : IUseCase
 {
     public async Task<FirewallHardwareReport> ExecuteAsync()
     {
-        var hardware = await repository.GetAllAsync();
-        var firewalls = hardware.OfType<Firewall>();
+        var firewalls = await repository.GetAllOfTypeAsync<Firewall>();
 
         var rows = firewalls.Select(sw =>
         {

+ 4 - 7
RackPeek.Domain/Resources/Hardware/Firewalls/UpdateFirewallUseCase.cs → RackPeek.Domain/Resources/Firewalls/UpdateFirewallUseCase.cs

@@ -1,9 +1,9 @@
 using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
+using RackPeek.Domain.Persistence;
 
-namespace RackPeek.Domain.Resources.Hardware.Firewalls;
+namespace RackPeek.Domain.Resources.Firewalls;
 
-public class UpdateFirewallUseCase(IHardwareRepository repository) : IUseCase
+public class UpdateFirewallUseCase(IResourceCollection repository) : IUseCase
 {
     public async Task ExecuteAsync(
         string name,
@@ -30,10 +30,7 @@ public class UpdateFirewallUseCase(IHardwareRepository repository) : IUseCase
 
         if (poe.HasValue)
             firewallResource.Poe = poe.Value;
-        if (notes != null)
-        {
-            firewallResource.Notes = notes;
-        }
+        if (notes != null) firewallResource.Notes = notes;
         await repository.UpdateAsync(firewallResource);
     }
 }

+ 0 - 31
RackPeek.Domain/Resources/Hardware/AccessPoints/CloneAccessPointUseCase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops;
-
-public class CloneAccessPointUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string cloneName)
-    {
-        originalName = Normalize.HardwareName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-
-        cloneName = Normalize.HardwareName(cloneName);
-        ThrowIfInvalid.ResourceName(cloneName);
-        
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(cloneName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{cloneName}' already exists.");
-
-        var original = await repository.GetByNameAsync(originalName) as AccessPoint;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-        
-        var clone = Clone.DeepClone(original);
-        clone.Name = cloneName;
-        
-        await repository.AddAsync(clone);
-    }
-}

+ 0 - 27
RackPeek.Domain/Resources/Hardware/AccessPoints/DeleteAccessPointUseCase.cs

@@ -1,27 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
-
-public class DeleteAccessPointUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
-{
-    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.");
-
-        // Break link to dependants
-        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
-        foreach (var systemResource in dependants)
-        {
-            systemResource.RunsOn = null;
-            await systemsRepo.UpdateAsync(systemResource);
-        }
-
-        await repository.DeleteAsync(name);
-    }
-}

+ 0 - 28
RackPeek.Domain/Resources/Hardware/AccessPoints/DescribeAccessPointUseCase.cs

@@ -1,28 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
-
-public record AccessPointDescription(
-    string Name,
-    string? Model,
-    double? Speed
-);
-
-public class DescribeAccessPointUseCase(IHardwareRepository repository) : IUseCase
-{
-    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)
-            throw new NotFoundException($"Access point '{name}' not found.");
-
-        return new AccessPointDescription(
-            ap.Name,
-            ap.Model,
-            ap.Speed
-        );
-    }
-}

+ 0 - 17
RackPeek.Domain/Resources/Hardware/AccessPoints/GetAccessPointUseCase.cs

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

+ 0 - 12
RackPeek.Domain/Resources/Hardware/AccessPoints/GetAccessPointsUseCase.cs

@@ -1,12 +0,0 @@
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
-
-public class GetAccessPointsUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task<IReadOnlyList<AccessPoint>> ExecuteAsync()
-    {
-        var hardware = await repository.GetAllAsync();
-        return hardware.OfType<AccessPoint>().ToList();
-    }
-}

+ 0 - 37
RackPeek.Domain/Resources/Hardware/AccessPoints/RenameAccessPointUseCase.cs

@@ -1,37 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.AccessPoints;
-
-public class RenameAccessPointUseCase(IHardwareRepository repository, ISystemRepository systemRepo, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string newName)
-    {
-        originalName = Normalize.SystemName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-        
-        newName = Normalize.SystemName(newName);
-        ThrowIfInvalid.ResourceName(newName);
-
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(newName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{newName}' already exists.");
-        
-        var original = await repository.GetByNameAsync(originalName) as AccessPoint;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-
-        original.Name = newName;
-        await repository.UpdateAsync(original);
-        
-        var children = await systemRepo.GetByPhysicalHostAsync(originalName);
-        foreach (var child in children)
-        {
-            child.RunsOn = newName;
-            await systemRepo.UpdateAsync(child);
-        }
-    }
-}

+ 0 - 29
RackPeek.Domain/Resources/Hardware/Desktops/AddDesktopUseCase.cs

@@ -1,29 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops;
-
-public class AddDesktopUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string name)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
-
-        var desktop = new Desktop
-        {
-            Name = name,
-            Cpus = new List<Cpu>(),
-            Drives = new List<Drive>(),
-            Nics = new List<Nic>(),
-            Gpus = new List<Gpu>(),
-            Ram = null
-        };
-
-        await repository.AddAsync(desktop);
-    }
-}

+ 0 - 31
RackPeek.Domain/Resources/Hardware/Desktops/CloneDesktopUsecase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops;
-
-public class CloneDesktopUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string cloneName)
-    {
-        originalName = Normalize.HardwareName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-
-        cloneName = Normalize.HardwareName(cloneName);
-        ThrowIfInvalid.ResourceName(cloneName);
-        
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(cloneName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{cloneName}' already exists.");
-
-        var original = await repository.GetByNameAsync(originalName) as Desktop;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-        
-        var clone = Clone.DeepClone(original);
-        clone.Name = cloneName;
-        
-        await repository.AddAsync(clone);
-    }
-}

+ 0 - 30
RackPeek.Domain/Resources/Hardware/Desktops/Cpus/AddDesktopCpuUseCase.cs

@@ -1,30 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Cpus;
-
-public class AddDesktopCpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, string? model, int? cores, int? threads)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        // ToDo validate / normalize all inputs
-
-        var cpu = new Cpu
-        {
-            Model = model,
-            Cores = cores,
-            Threads = threads
-        };
-
-        var desktop = await repository.GetByNameAsync(name) as Desktop
-                      ?? throw new InvalidOperationException($"Desktop '{name}' not found.");
-
-        desktop.Cpus ??= new List<Cpu>();
-        desktop.Cpus.Add(cpu);
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 23
RackPeek.Domain/Resources/Hardware/Desktops/Cpus/RemoveDesktopCpuUseCase.cs

@@ -1,23 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Cpus;
-
-public class RemoveDesktopCpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        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 NotFoundException($"CPU index {index} not found on desktop '{name}'.");
-
-        desktop.Cpus.RemoveAt(index);
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 34
RackPeek.Domain/Resources/Hardware/Desktops/Cpus/UpdateDesktopCpuUseCase.cs

@@ -1,34 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Cpus;
-
-public class UpdateDesktopCpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? model,
-        int? cores,
-        int? threads)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        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 NotFoundException($"CPU index {index} not found on desktop '{name}'.");
-
-        var cpu = desktop.Cpus[index];
-        cpu.Model = model;
-        cpu.Cores = cores;
-        cpu.Threads = threads;
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 27
RackPeek.Domain/Resources/Hardware/Desktops/DeleteDesktopUseCase.cs

@@ -1,27 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops;
-
-public class DeleteDesktopUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string name)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name);
-        if (hardware == null)
-            throw new NotFoundException($"Desktop '{name}' not found.");
-
-        // Break link to dependants
-        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
-        foreach (var systemResource in dependants)
-        {
-            systemResource.RunsOn = null;
-            await systemsRepo.UpdateAsync(systemResource);
-        }
-
-        await repository.DeleteAsync(name);
-    }
-}

+ 0 - 31
RackPeek.Domain/Resources/Hardware/Desktops/Drives/AddDesktopDriveUseCase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Drives;
-
-public class AddDesktopDriveUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? type,
-        int? size)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        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(new Drive
-        {
-            Type = type,
-            Size = size
-        });
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 23
RackPeek.Domain/Resources/Hardware/Desktops/Drives/RemoveDesktopDriveUseCase.cs

@@ -1,23 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Drives;
-
-public class RemoveDesktopDriveUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        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 NotFoundException($"Drive index {index} not found on desktop '{name}'.");
-
-        desktop.Drives.RemoveAt(index);
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 28
RackPeek.Domain/Resources/Hardware/Desktops/Drives/UpdateDesktopDriveUseCase.cs

@@ -1,28 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Drives;
-
-public class UpdateDesktopDriveUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index, string? type, int? size)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        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 NotFoundException($"Drive index {index} not found on desktop '{name}'.");
-
-        var drive = desktop.Drives[index];
-        drive.Type = type;
-        drive.Size = size;
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 18
RackPeek.Domain/Resources/Hardware/Desktops/GetDesktopUseCase.cs

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

+ 0 - 12
RackPeek.Domain/Resources/Hardware/Desktops/GetDesktopsUseCase.cs

@@ -1,12 +0,0 @@
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops;
-
-public class GetDesktopsUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task<IReadOnlyList<Desktop>> ExecuteAsync()
-    {
-        var hardware = await repository.GetAllAsync();
-        return hardware.OfType<Desktop>().ToList();
-    }
-}

+ 0 - 30
RackPeek.Domain/Resources/Hardware/Desktops/Gpus/AddDesktopGpuUseCase.cs

@@ -1,30 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
-
-public class AddDesktopGpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? model,
-        int? vram)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        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(new Gpu
-        {
-            Model = model,
-            Vram = vram
-        });
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 23
RackPeek.Domain/Resources/Hardware/Desktops/Gpus/RemoveDesktopGpuUseCase.cs

@@ -1,23 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
-
-public class RemoveDesktopGpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        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 NotFoundException($"GPU index {index} not found on desktop '{name}'.");
-
-        desktop.Gpus.RemoveAt(index);
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 31
RackPeek.Domain/Resources/Hardware/Desktops/Gpus/UpdateDesktopGpuUseCase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Gpus;
-
-public class UpdateDesktopGpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? model,
-        int? vram)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        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 NotFoundException($"GPU index {index} not found on desktop '{name}'.");
-
-        var gpu = desktop.Gpus[index];
-        gpu.Model = model;
-        gpu.Vram = vram;
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 35
RackPeek.Domain/Resources/Hardware/Desktops/Nics/AddDesktopNicUseCase.cs

@@ -1,35 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Nics;
-
-public class AddDesktopNicUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? type,
-        double? speed,
-        int? ports)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var nicType = Normalize.NicType(type);
-        ThrowIfInvalid.NicType(nicType);
-
-        var desktop = await repository.GetByNameAsync(name) as Desktop
-                      ?? throw new NotFoundException($"Desktop '{name}' not found.");
-
-        desktop.Nics ??= new List<Nic>();
-        desktop.Nics.Add(new Nic
-        {
-            Type = nicType,
-            Speed = speed,
-            Ports = ports
-        });
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 23
RackPeek.Domain/Resources/Hardware/Desktops/Nics/RemoveDesktopNicUseCase.cs

@@ -1,23 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Nics;
-
-public class RemoveDesktopNicUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        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 NotFoundException($"NIC index {index} not found on desktop '{name}'.");
-
-        desktop.Nics.RemoveAt(index);
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 37
RackPeek.Domain/Resources/Hardware/Desktops/Nics/UpdateDesktopNicUseCase.cs

@@ -1,37 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops.Nics;
-
-public class UpdateDesktopNicUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? type,
-        double? speed,
-        int? ports)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var nicType = Normalize.NicType(type);
-        ThrowIfInvalid.NicType(nicType);
-
-        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 NotFoundException($"NIC index {index} not found on desktop '{name}'.");
-
-        var nic = desktop.Nics[index];
-        nic.Type = nicType;
-        nic.Speed = speed;
-        nic.Ports = ports;
-
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 37
RackPeek.Domain/Resources/Hardware/Desktops/RenameDesktopUseCase.cs

@@ -1,37 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Desktops;
-
-public class RenameDesktopUseCase(IHardwareRepository repository, ISystemRepository systemRepo, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string newName)
-    {
-        originalName = Normalize.SystemName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-        
-        newName = Normalize.SystemName(newName);
-        ThrowIfInvalid.ResourceName(newName);
-
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(newName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{newName}' already exists.");
-        
-        var original = await repository.GetByNameAsync(originalName) as Desktop;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-
-        original.Name = newName;
-        await repository.UpdateAsync(original);
-        
-        var children = await systemRepo.GetByPhysicalHostAsync(originalName);
-        foreach (var child in children)
-        {
-            child.RunsOn = newName;
-            await systemRepo.UpdateAsync(child);
-        }
-    }
-}

+ 0 - 24
RackPeek.Domain/Resources/Hardware/Firewalls/AddFirewallUseCase.cs

@@ -1,24 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Firewalls;
-
-public class AddFirewallUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string name)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
-
-        var firewallResource = new Firewall
-        {
-            Name = name
-        };
-
-        await repository.AddAsync(firewallResource);
-    }
-}

+ 0 - 31
RackPeek.Domain/Resources/Hardware/Firewalls/CloneFirewallUseCase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Firewalls;
-
-public class CloneFirewallUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string cloneName)
-    {
-        originalName = Normalize.HardwareName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-
-        cloneName = Normalize.HardwareName(cloneName);
-        ThrowIfInvalid.ResourceName(cloneName);
-        
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(cloneName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{cloneName}' already exists.");
-
-        var original = await repository.GetByNameAsync(originalName) as Firewall;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-        
-        var clone = Clone.DeepClone(original);
-        clone.Name = cloneName;
-        
-        await repository.AddAsync(clone);
-    }
-}

+ 0 - 27
RackPeek.Domain/Resources/Hardware/Firewalls/DeleteFirewallUseCase.cs

@@ -1,27 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Firewalls;
-
-public class DeleteFirewallUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string name)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        if (await repository.GetByNameAsync(name) is not Firewall hardware)
-            throw new NotFoundException($"Firewall '{name}' not found.");
-
-        // Break link to dependants
-        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
-        foreach (var systemResource in dependants)
-        {
-            systemResource.RunsOn = null;
-            await systemsRepo.UpdateAsync(systemResource);
-        }
-
-        await repository.DeleteAsync(name);
-    }
-}

+ 0 - 18
RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallUseCase.cs

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

+ 0 - 12
RackPeek.Domain/Resources/Hardware/Firewalls/GetFirewallsUseCase.cs

@@ -1,12 +0,0 @@
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Firewalls;
-
-public class GetFirewallsUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task<IReadOnlyList<Firewall>> ExecuteAsync()
-    {
-        var hardware = await repository.GetAllAsync();
-        return hardware.OfType<Firewall>().ToList();
-    }
-}

+ 0 - 35
RackPeek.Domain/Resources/Hardware/Firewalls/Ports/AddFirewallPortUseCase.cs

@@ -1,35 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Firewalls.Ports;
-
-public class AddFirewallPortUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? type,
-        double? speed,
-        int? ports)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var nicType = Normalize.NicType(type);
-        ThrowIfInvalid.NicType(nicType);
-
-        var desktop = await repository.GetByNameAsync(name) as Firewall
-                      ?? throw new NotFoundException($"Firewall '{name}' not found.");
-
-        desktop.Ports ??= new List<Port>();
-        desktop.Ports.Add(new Port
-        {
-            Type = nicType,
-            Speed = speed,
-            Count = ports
-        });
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 23
RackPeek.Domain/Resources/Hardware/Firewalls/Ports/RemoveFirewallPortUseCase.cs

@@ -1,23 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Firewalls.Ports;
-
-public class RemoveFirewallPortUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var firewall = await repository.GetByNameAsync(name) as Firewall
-                       ?? throw new NotFoundException($"Firewall '{name}' not found.");
-
-        if (firewall.Ports == null || index < 0 || index >= firewall.Ports.Count)
-            throw new NotFoundException($"Port index {index} not found on firewall '{name}'.");
-
-        firewall.Ports.RemoveAt(index);
-
-        await repository.UpdateAsync(firewall);
-    }
-}

+ 0 - 37
RackPeek.Domain/Resources/Hardware/Firewalls/Ports/UpdateFirewallPortUseCase.cs

@@ -1,37 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Firewalls.Ports;
-
-public class UpdateFirewallPortUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? type,
-        double? speed,
-        int? ports)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var nicType = Normalize.NicType(type);
-        ThrowIfInvalid.NicType(nicType);
-
-        var firewall = await repository.GetByNameAsync(name) as Firewall
-                       ?? throw new NotFoundException($"Firewall '{name}' not found.");
-
-        if (firewall.Ports == null || index < 0 || index >= firewall.Ports.Count)
-            throw new NotFoundException($"Port index {index} not found on firewall '{name}'.");
-
-        var nic = firewall.Ports[index];
-        nic.Type = nicType;
-        nic.Speed = speed;
-        nic.Count = ports;
-
-        await repository.UpdateAsync(firewall);
-    }
-}

+ 0 - 37
RackPeek.Domain/Resources/Hardware/Firewalls/RenameFirewallUseCase.cs

@@ -1,37 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Firewalls;
-
-public class RenameFirewallUseCase(IHardwareRepository repository, ISystemRepository systemRepo, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string newName)
-    {
-        originalName = Normalize.SystemName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-        
-        newName = Normalize.SystemName(newName);
-        ThrowIfInvalid.ResourceName(newName);
-
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(newName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{newName}' already exists.");
-        
-        var original = await repository.GetByNameAsync(originalName) as Firewall;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-
-        original.Name = newName;
-        await repository.UpdateAsync(original);
-        
-        var children = await systemRepo.GetByPhysicalHostAsync(originalName);
-        foreach (var child in children)
-        {
-            child.RunsOn = newName;
-            await systemRepo.UpdateAsync(child);
-        }
-    }
-}

+ 11 - 11
RackPeek.Domain/Resources/Hardware/GetHardwareSystemTreeUseCase.cs

@@ -1,46 +1,46 @@
 using RackPeek.Domain.Helpers;
+using RackPeek.Domain.Persistence;
 using RackPeek.Domain.Resources.Services;
 using RackPeek.Domain.Resources.SystemResources;
 
 namespace RackPeek.Domain.Resources.Hardware;
 
 public class GetHardwareSystemTreeUseCase(
-    IHardwareRepository hardwareRepository,
-    ISystemRepository systemRepository,
-    IServiceRepository serviceRepository) : IUseCase
+    IResourceCollection repo) : IUseCase
 {
     public async Task<HardwareDependencyTree> ExecuteAsync(string hardwareName)
     {
         ThrowIfInvalid.ResourceName(hardwareName);
 
-        var server = await hardwareRepository.GetByNameAsync(hardwareName);
+        var server = await repo.GetByNameAsync(hardwareName) as Hardware;
         if (server is null)
             throw new NotFoundException($"Hardware '{hardwareName}' not found.");
 
         return await BuildDependencyTreeAsync(server);
     }
 
-    private async Task<HardwareDependencyTree> BuildDependencyTreeAsync(Models.Hardware server)
+    private async Task<HardwareDependencyTree> BuildDependencyTreeAsync(Hardware server)
     {
-        var systems = await systemRepository.GetByPhysicalHostAsync(server.Name);
+        var systems = await repo.GetDependantsAsync(server.Name);
 
         var systemTrees = new List<SystemDependencyTree>();
-        foreach (var system in systems) systemTrees.Add(await BuildSystemDependencyTreeAsync(system));
+        foreach (var system in systems.OfType<SystemResource>())
+            systemTrees.Add(await BuildSystemDependencyTreeAsync(system));
 
         return new HardwareDependencyTree(server, systemTrees);
     }
 
     private async Task<SystemDependencyTree> BuildSystemDependencyTreeAsync(SystemResource system)
     {
-        var services = await serviceRepository.GetBySystemHostAsync(system.Name);
+        var services = await repo.GetDependantsAsync(system.Name);
 
-        return new SystemDependencyTree(system, services);
+        return new SystemDependencyTree(system, services.OfType<Service>());
     }
 }
 
-public sealed class HardwareDependencyTree(Models.Hardware hardware, IEnumerable<SystemDependencyTree> systems)
+public sealed class HardwareDependencyTree(Hardware hardware, IEnumerable<SystemDependencyTree> systems)
 {
-    public Models.Hardware Hardware { get; } = hardware;
+    public Hardware Hardware { get; } = hardware;
     public IEnumerable<SystemDependencyTree> Systems { get; } = systems;
 }
 

+ 5 - 0
RackPeek.Domain/Resources/Hardware/Hardware.cs

@@ -0,0 +1,5 @@
+namespace RackPeek.Domain.Resources.Hardware;
+
+public abstract class Hardware : Resource
+{
+}

+ 2 - 5
RackPeek.Domain/Resources/Hardware/IHardwareRepository.cs

@@ -1,13 +1,10 @@
-using RackPeek.Domain.Resources.Services;
-using RackPeek.Domain.Resources.SystemResources;
-
 namespace RackPeek.Domain.Resources.Hardware;
 
-public interface IHardwareRepository : IResourceRepo<Models.Hardware> 
+public interface IHardwareRepository
 {
     Task<int> GetCountAsync();
     Task<Dictionary<string, int>> GetKindCountAsync();
-    
+
     public Task<List<HardwareTree>> GetTreeAsync();
 }
 

+ 0 - 28
RackPeek.Domain/Resources/Hardware/Laptops/AddLaptopUseCase.cs

@@ -1,28 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops;
-
-public class AddLaptopUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string name)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
-
-        var laptop = new Laptop
-        {
-            Name = name,
-            Cpus = new List<Cpu>(),
-            Drives = new List<Drive>(),
-            Gpus = new List<Gpu>(),
-            Ram = null
-        };
-
-        await repository.AddAsync(laptop);
-    }
-}

+ 0 - 31
RackPeek.Domain/Resources/Hardware/Laptops/CloneLaptopUseCase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops;
-
-public class CloneLaptopUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string cloneName)
-    {
-        originalName = Normalize.HardwareName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-
-        cloneName = Normalize.HardwareName(cloneName);
-        ThrowIfInvalid.ResourceName(cloneName);
-        
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(cloneName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{cloneName}' already exists.");
-
-        var original = await repository.GetByNameAsync(originalName) as Laptop;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-        
-        var clone = Clone.DeepClone(original);
-        clone.Name = cloneName;
-        
-        await repository.AddAsync(clone);
-    }
-}

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

@@ -1,23 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Cpus;
-
-public class AddLaptopCpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, string? model, int? cores, int? threads)
-    {
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
-
-        laptop.Cpus ??= new List<Cpu>();
-        laptop.Cpus.Add(new Cpu { Model = model, Cores = cores, Threads = threads });
-
-        await repository.UpdateAsync(laptop);
-    }
-}

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

@@ -1,23 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Cpus;
-
-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
-                     ?? throw new InvalidOperationException($"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}'.");
-
-        laptop.Cpus.RemoveAt(index);
-
-        await repository.UpdateAsync(laptop);
-    }
-}

+ 0 - 32
RackPeek.Domain/Resources/Hardware/Laptops/Cpus/UpdateDesktopCpuUseCase.cs

@@ -1,32 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Cpus;
-
-public class UpdateLaptopCpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? model,
-        int? cores,
-        int? threads)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-        var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
-
-        if (laptop.Cpus == null || index < 0 || index >= laptop.Cpus.Count)
-            throw new NotFoundException($"CPU index {index} not found on Laptop '{name}'.");
-
-        var cpu = laptop.Cpus[index];
-        cpu.Model = model;
-        cpu.Cores = cores;
-        cpu.Threads = threads;
-        await repository.UpdateAsync(laptop);
-    }
-}

+ 0 - 27
RackPeek.Domain/Resources/Hardware/Laptops/DeleteLaptopUseCase.cs

@@ -1,27 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops;
-
-public class DeleteLaptopUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string name)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name);
-        if (hardware == null)
-            throw new NotFoundException($"Laptop '{name}' not found.");
-
-        // Break link to dependants
-        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
-        foreach (var systemResource in dependants)
-        {
-            systemResource.RunsOn = null;
-            await systemsRepo.UpdateAsync(systemResource);
-        }
-
-        await repository.DeleteAsync(name);
-    }
-}

+ 0 - 30
RackPeek.Domain/Resources/Hardware/Laptops/Drives/AddDesktopDriveUseCase.cs

@@ -1,30 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Drives;
-
-public class AddLaptopDriveUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? type,
-        int? size)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
-
-        laptop.Drives ??= new List<Drive>();
-        laptop.Drives.Add(new Drive
-        {
-            Type = type,
-            Size = size
-        });
-        await repository.UpdateAsync(laptop);
-    }
-}

+ 0 - 22
RackPeek.Domain/Resources/Hardware/Laptops/Drives/RemoveDesktopDriveUseCase.cs

@@ -1,22 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Drives;
-
-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 NotFoundException($"Laptop '{name}' not found.");
-
-        if (laptop.Drives == null || index < 0 || index >= laptop.Drives.Count)
-            throw new NotFoundException($"Drive index {index} not found on Laptop '{name}'.");
-
-        laptop.Drives.RemoveAt(index);
-
-        await repository.UpdateAsync(laptop);
-    }
-}

+ 0 - 28
RackPeek.Domain/Resources/Hardware/Laptops/Drives/UpdateDesktopDriveUseCase.cs

@@ -1,28 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Drives;
-
-public class UpdateLaptopDriveUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index, string? type, int? size)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
-
-        if (laptop.Drives == null || index < 0 || index >= laptop.Drives.Count)
-            throw new NotFoundException($"Drive index {index} not found on Laptop '{name}'.");
-
-        var drive = laptop.Drives[index];
-        drive.Type = type;
-        drive.Size = size;
-
-        await repository.UpdateAsync(laptop);
-    }
-}

+ 0 - 18
RackPeek.Domain/Resources/Hardware/Laptops/GetDesktopUseCase.cs

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

+ 0 - 12
RackPeek.Domain/Resources/Hardware/Laptops/GetLaptopsUseCase.cs

@@ -1,12 +0,0 @@
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops;
-
-public class GetLaptopsUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task<IReadOnlyList<Laptop>> ExecuteAsync()
-    {
-        var hardware = await repository.GetAllAsync();
-        return hardware.OfType<Laptop>().ToList();
-    }
-}

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

@@ -1,29 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
-
-public class AddLaptopGpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? model,
-        int? vram)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-        var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new InvalidOperationException($"Laptop '{name}' not found.");
-
-        laptop.Gpus ??= new List<Gpu>();
-        laptop.Gpus.Add(new Gpu
-        {
-            Model = model,
-            Vram = vram
-        });
-        await repository.UpdateAsync(laptop);
-    }
-}

+ 0 - 22
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/RemoveDesktopGpuUseCase.cs

@@ -1,22 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
-
-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 NotFoundException($"Laptop '{name}' not found.");
-
-        if (laptop.Gpus == null || index < 0 || index >= laptop.Gpus.Count)
-            throw new NotFoundException($"GPU index {index} not found on Laptop '{name}'.");
-
-        laptop.Gpus.RemoveAt(index);
-
-        await repository.UpdateAsync(laptop);
-    }
-}

+ 0 - 31
RackPeek.Domain/Resources/Hardware/Laptops/Gpus/UpdateDesktopGpuUseCase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops.Gpus;
-
-public class UpdateLaptopGpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? model,
-        int? vram)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var laptop = await repository.GetByNameAsync(name) as Laptop
-                     ?? throw new NotFoundException($"Laptop '{name}' not found.");
-
-        if (laptop.Gpus == null || index < 0 || index >= laptop.Gpus.Count)
-            throw new NotFoundException($"GPU index {index} not found on Laptop '{name}'.");
-
-        var gpu = laptop.Gpus[index];
-        gpu.Model = model;
-        gpu.Vram = vram;
-        await repository.UpdateAsync(laptop);
-    }
-}

+ 0 - 37
RackPeek.Domain/Resources/Hardware/Laptops/RenameLaptopUseCase.cs

@@ -1,37 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Laptops;
-
-public class RenameLaptopUseCase(IHardwareRepository repository, ISystemRepository systemRepo, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string newName)
-    {
-        originalName = Normalize.SystemName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-        
-        newName = Normalize.SystemName(newName);
-        ThrowIfInvalid.ResourceName(newName);
-
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(newName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{newName}' already exists.");
-        
-        var original = await repository.GetByNameAsync(originalName) as Laptop;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-
-        original.Name = newName;
-        await repository.UpdateAsync(original);
-        
-        var children = await systemRepo.GetByPhysicalHostAsync(originalName);
-        foreach (var child in children)
-        {
-            child.RunsOn = newName;
-            await systemRepo.UpdateAsync(child);
-        }
-    }
-}

+ 0 - 24
RackPeek.Domain/Resources/Hardware/Routers/AddRouterUseCase.cs

@@ -1,24 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Routers;
-
-public class AddRouterUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string name)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
-
-        var routerResource = new Router
-        {
-            Name = name
-        };
-
-        await repository.AddAsync(routerResource);
-    }
-}

+ 0 - 31
RackPeek.Domain/Resources/Hardware/Routers/CloneRouterUseCase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Routers;
-
-public class CloneRouterUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string cloneName)
-    {
-        originalName = Normalize.HardwareName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-
-        cloneName = Normalize.HardwareName(cloneName);
-        ThrowIfInvalid.ResourceName(cloneName);
-        
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(cloneName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{cloneName}' already exists.");
-
-        var original = await repository.GetByNameAsync(originalName) as Router;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-        
-        var clone = Clone.DeepClone(original);
-        clone.Name = cloneName;
-        
-        await repository.AddAsync(clone);
-    }
-}

+ 0 - 27
RackPeek.Domain/Resources/Hardware/Routers/DeleteRouterUseCase.cs

@@ -1,27 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Routers;
-
-public class DeleteRouterUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string name)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        if (await repository.GetByNameAsync(name) is not Router hardware)
-            throw new NotFoundException($"Router '{name}' not found.");
-
-        // Break link to dependants
-        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
-        foreach (var systemResource in dependants)
-        {
-            systemResource.RunsOn = null;
-            await systemsRepo.UpdateAsync(systemResource);
-        }
-
-        await repository.DeleteAsync(name);
-    }
-}

+ 0 - 18
RackPeek.Domain/Resources/Hardware/Routers/GetRouterUseCase.cs

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

+ 0 - 12
RackPeek.Domain/Resources/Hardware/Routers/GetRoutersUseCase.cs

@@ -1,12 +0,0 @@
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Routers;
-
-public class GetRoutersUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task<IReadOnlyList<Router>> ExecuteAsync()
-    {
-        var hardware = await repository.GetAllAsync();
-        return hardware.OfType<Router>().ToList();
-    }
-}

+ 0 - 35
RackPeek.Domain/Resources/Hardware/Routers/Ports/AddRouterPortUseCase.cs

@@ -1,35 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Routers.Ports;
-
-public class AddRouterPortUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? type,
-        double? speed,
-        int? ports)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var nicType = Normalize.NicType(type);
-        ThrowIfInvalid.NicType(nicType);
-
-        var desktop = await repository.GetByNameAsync(name) as Router
-                      ?? throw new NotFoundException($"Router '{name}' not found.");
-
-        desktop.Ports ??= new List<Port>();
-        desktop.Ports.Add(new Port
-        {
-            Type = nicType,
-            Speed = speed,
-            Count = ports
-        });
-        await repository.UpdateAsync(desktop);
-    }
-}

+ 0 - 23
RackPeek.Domain/Resources/Hardware/Routers/Ports/RemoveRouterPortUseCase.cs

@@ -1,23 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Routers.Ports;
-
-public class RemoveRouterPortUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var Router = await repository.GetByNameAsync(name) as Router
-                     ?? throw new NotFoundException($"Router '{name}' not found.");
-
-        if (Router.Ports == null || index < 0 || index >= Router.Ports.Count)
-            throw new NotFoundException($"Port index {index} not found on Router '{name}'.");
-
-        Router.Ports.RemoveAt(index);
-
-        await repository.UpdateAsync(Router);
-    }
-}

+ 0 - 37
RackPeek.Domain/Resources/Hardware/Routers/Ports/UpdateRouterPortUseCase.cs

@@ -1,37 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Routers.Ports;
-
-public class UpdateRouterPortUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? type,
-        double? speed,
-        int? ports)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var nicType = Normalize.NicType(type);
-        ThrowIfInvalid.NicType(nicType);
-
-        var Router = await repository.GetByNameAsync(name) as Router
-                     ?? throw new NotFoundException($"Router '{name}' not found.");
-
-        if (Router.Ports == null || index < 0 || index >= Router.Ports.Count)
-            throw new NotFoundException($"Port index {index} not found on Router '{name}'.");
-
-        var nic = Router.Ports[index];
-        nic.Type = nicType;
-        nic.Speed = speed;
-        nic.Count = ports;
-
-        await repository.UpdateAsync(Router);
-    }
-}

+ 0 - 37
RackPeek.Domain/Resources/Hardware/Routers/RenameRouterUseCase.cs

@@ -1,37 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Routers;
-
-public class RenameRouterUseCase(IHardwareRepository repository, ISystemRepository systemRepo, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string newName)
-    {
-        originalName = Normalize.SystemName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-        
-        newName = Normalize.SystemName(newName);
-        ThrowIfInvalid.ResourceName(newName);
-
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(newName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{newName}' already exists.");
-        
-        var original = await repository.GetByNameAsync(originalName) as Router;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-
-        original.Name = newName;
-        await repository.UpdateAsync(original);
-        
-        var children = await systemRepo.GetByPhysicalHostAsync(originalName);
-        foreach (var child in children)
-        {
-            child.RunsOn = newName;
-            await systemRepo.UpdateAsync(child);
-        }
-    }
-}

+ 0 - 24
RackPeek.Domain/Resources/Hardware/Servers/AddServerUseCase.cs

@@ -1,24 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers;
-
-public class AddServerUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string name)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(name);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{name}' already exists.");
-
-        var server = new Server
-        {
-            Name = name
-        };
-
-        await repository.AddAsync(server);
-    }
-}

+ 0 - 31
RackPeek.Domain/Resources/Hardware/Servers/CloneServerUseCase.cs

@@ -1,31 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers;
-
-public class CloneServerUseCase(IHardwareRepository repository, IResourceRepository resourceRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string originalName, string cloneName)
-    {
-        originalName = Normalize.HardwareName(originalName);
-        ThrowIfInvalid.ResourceName(originalName);
-
-        cloneName = Normalize.HardwareName(cloneName);
-        ThrowIfInvalid.ResourceName(cloneName);
-        
-        var existingResourceKind = await resourceRepo.GetResourceKindAsync(cloneName);
-        if (!string.IsNullOrEmpty(existingResourceKind))
-            throw new ConflictException($"{existingResourceKind} resource '{cloneName}' already exists.");
-
-        var original = await repository.GetByNameAsync(originalName) as Server;
-        if (original == null)
-        {
-            throw new NotFoundException($"Resource '{originalName}' not found.");
-        }
-        
-        var clone = Clone.DeepClone(original);
-        clone.Name = cloneName;
-        
-        await repository.AddAsync(clone);
-    }
-}

+ 0 - 35
RackPeek.Domain/Resources/Hardware/Servers/Cpus/AddCpuUseCase.cs

@@ -1,35 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Cpus;
-
-public class AddCpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? model,
-        int? cores,
-        int? threads)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name);
-
-        if (hardware is not Server server) return;
-
-        server.Cpus ??= [];
-
-        server.Cpus.Add(new Cpu
-        {
-            Model = model,
-            Cores = cores,
-            Threads = threads
-        });
-
-        await repository.UpdateAsync(server);
-    }
-}

+ 0 - 27
RackPeek.Domain/Resources/Hardware/Servers/Cpus/RemoveCpuUseCase.cs

@@ -1,27 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Cpus;
-
-public class RemoveCpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name);
-        if (hardware is not Server server) return;
-
-        server.Cpus ??= [];
-
-        if (index < 0 || index >= server.Cpus.Count)
-            throw new ArgumentOutOfRangeException(nameof(index), "CPU index out of range.");
-
-        server.Cpus.RemoveAt(index);
-
-        await repository.UpdateAsync(server);
-    }
-}

+ 0 - 37
RackPeek.Domain/Resources/Hardware/Servers/Cpus/UpdateCpuUseCase.cs

@@ -1,37 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Cpus;
-
-public class UpdateCpuUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        int index,
-        string? model,
-        int? cores,
-        int? threads)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name);
-
-        if (hardware is not Server server) return;
-
-        server.Cpus ??= [];
-
-        if (index < 0 || index >= server.Cpus.Count)
-            throw new ArgumentOutOfRangeException(nameof(index), "CPU index out of range.");
-
-        var cpu = server.Cpus[index];
-        cpu.Model = model;
-        cpu.Cores = cores;
-        cpu.Threads = threads;
-
-        await repository.UpdateAsync(server);
-    }
-}

+ 0 - 28
RackPeek.Domain/Resources/Hardware/Servers/DeleteServerUseCase.cs

@@ -1,28 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-using RackPeek.Domain.Resources.SystemResources;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers;
-
-public class DeleteServerUseCase(IHardwareRepository repository, ISystemRepository systemsRepo) : IUseCase
-{
-    public async Task ExecuteAsync(string name)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name) as Server;
-        if (hardware == null)
-            throw new NotFoundException($"Server '{name}' not found.");
-
-        // Break link to dependants
-        var dependants = await systemsRepo.GetByPhysicalHostAsync(name);
-        foreach (var systemResource in dependants)
-        {
-            systemResource.RunsOn = null;
-            await systemsRepo.UpdateAsync(systemResource);
-        }
-
-        await repository.DeleteAsync(name);
-    }
-}

+ 0 - 34
RackPeek.Domain/Resources/Hardware/Servers/Drives/AddDriveUseCase.cs

@@ -1,34 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Drives;
-
-public class AddDrivesUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(
-        string name,
-        string? type,
-        int? size)
-    {
-        // ToDo pass in properties as inputs, construct the entity in the usecase, ensure optional inputs are nullable
-        // ToDo validate / normalize all inputs
-
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name);
-
-        if (hardware is not Server server)
-            throw new NotFoundException($"Server '{name}' not found.");
-
-        server.Drives ??= [];
-
-        server.Drives.Add(new Drive
-        {
-            Type = type,
-            Size = size
-        });
-
-        await repository.UpdateAsync(server);
-    }
-}

+ 0 - 24
RackPeek.Domain/Resources/Hardware/Servers/Drives/RemoveDriveUseCase.cs

@@ -1,24 +0,0 @@
-using RackPeek.Domain.Helpers;
-using RackPeek.Domain.Resources.Models;
-
-namespace RackPeek.Domain.Resources.Hardware.Servers.Drives;
-
-public class RemoveDriveUseCase(IHardwareRepository repository) : IUseCase
-{
-    public async Task ExecuteAsync(string name, int index)
-    {
-        name = Normalize.HardwareName(name);
-        ThrowIfInvalid.ResourceName(name);
-
-        var hardware = await repository.GetByNameAsync(name);
-        if (hardware is not Server server)
-            throw new NotFoundException($"Server '{name}' not found.");
-
-        server.Drives ??= [];
-        if (index < 0 || index >= server.Drives.Count)
-            throw new ArgumentOutOfRangeException(nameof(index), "Drive index out of range.");
-
-        server.Drives.RemoveAt(index);
-        await repository.UpdateAsync(server);
-    }
-}

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio