diff --git a/.idea/.idea.WyvernInventory/.idea/.gitignore b/.idea/.idea.WyvernInventory/.idea/.gitignore
new file mode 100644
index 0000000..89b3766
--- /dev/null
+++ b/.idea/.idea.WyvernInventory/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/modules.xml
+/projectSettingsUpdater.xml
+/contentModel.xml
+/.idea.WyvernInventory.iml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.WyvernInventory/.idea/encodings.xml b/.idea/.idea.WyvernInventory/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/.idea/.idea.WyvernInventory/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.WyvernInventory/.idea/indexLayout.xml b/.idea/.idea.WyvernInventory/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.WyvernInventory/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WyvernInventory.API/Controllers/InventoryController.cs b/WyvernInventory.API/Controllers/InventoryController.cs
new file mode 100644
index 0000000..a9569f5
--- /dev/null
+++ b/WyvernInventory.API/Controllers/InventoryController.cs
@@ -0,0 +1,40 @@
+using Microsoft.AspNetCore.Mvc;
+using WyvernInventory.Core.Interfaces.Services;
+using WyvernInventory.Core.Models;
+
+namespace WyvernInventory.API.Controllers;
+
+[ApiController]
+[Route("[controller]")]
+public class InventoryController(IDataObjectService genericItemService)
+ : ControllerBase
+{
+ private readonly IDataObjectService _genericItemService = genericItemService;
+
+ [HttpPost]
+ public List Get([FromBody] List items)
+ {
+ return _genericItemService.GetAsync(_ => _).Result;
+ }
+
+ [HttpPost("upsert")]
+ public IResult Post([FromBody] List items)
+ {
+ (int, int) results = _genericItemService.UpsertAsync(items).Result;
+
+ if (results.Item1 > 0 || results.Item2 > 0)
+ {
+ return Results.StatusCode(201);
+ }
+
+ return Results.Ok();
+ }
+
+ [HttpDelete("{id}")]
+ public IResult Delete([FromRoute] int id)
+ {
+ _genericItemService.DeleteAsync(new() { new() { Id = id } }).Wait();
+
+ return Results.Ok();
+ }
+}
\ No newline at end of file
diff --git a/WyvernInventory.API/Controllers/WeatherForecastController.cs b/WyvernInventory.API/Controllers/WeatherForecastController.cs
new file mode 100644
index 0000000..263c7f2
--- /dev/null
+++ b/WyvernInventory.API/Controllers/WeatherForecastController.cs
@@ -0,0 +1,37 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace WyvernInventory.API.Controllers;
+
+[ApiController]
+[Route("[controller]")]
+public class WeatherForecastController : ControllerBase
+{
+ private static readonly string[] Summaries =
+ [
+ "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
+ ];
+
+ [HttpGet(Name = "GetWeatherForecast")]
+ public IEnumerable GetBwah()
+ {
+ return Enumerable.Range(1, 5).Select(index => new WeatherForecast
+ {
+ Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
+ TemperatureC = Random.Shared.Next(-20, 55),
+ Summary = Summaries[Random.Shared.Next(Summaries.Length)]
+ })
+ .ToArray();
+ }
+
+ [HttpGet("bwah", Name = "GetWeatherForecastBwah")]
+ public IEnumerable Get()
+ {
+ return Enumerable.Range(1, 2).Select(index => new WeatherForecast
+ {
+ Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
+ TemperatureC = Random.Shared.Next(-20, 55),
+ Summary = Summaries[Random.Shared.Next(Summaries.Length)]
+ })
+ .ToArray();
+ }
+}
\ No newline at end of file
diff --git a/WyvernInventory.API/Dockerfile b/WyvernInventory.API/Dockerfile
new file mode 100644
index 0000000..e924ab1
--- /dev/null
+++ b/WyvernInventory.API/Dockerfile
@@ -0,0 +1,23 @@
+FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
+USER $APP_UID
+WORKDIR /app
+EXPOSE 8080
+EXPOSE 8081
+
+FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["WyvernInventory.API/WyvernInventory.API.csproj", "WyvernInventory.API/"]
+RUN dotnet restore "WyvernInventory.API/WyvernInventory.API.csproj"
+COPY . .
+WORKDIR "/src/WyvernInventory.API"
+RUN dotnet build "./WyvernInventory.API.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./WyvernInventory.API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "WyvernInventory.API.dll"]
diff --git a/WyvernInventory.API/Program.cs b/WyvernInventory.API/Program.cs
new file mode 100644
index 0000000..f6065fc
--- /dev/null
+++ b/WyvernInventory.API/Program.cs
@@ -0,0 +1,31 @@
+using WyvernInventory.Core.Interfaces.Repos;
+using WyvernInventory.Core.Interfaces.Services;
+using WyvernInventory.Core.Models;
+using WyvernInventory.Infrastructure.Repos;
+using WyvernInventory.Infrastructure.Services;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddControllers();
+// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
+builder.Services.AddOpenApi();
+builder.Services.AddSingleton, GenericInventoryItemRepo>();
+builder.Services.AddSingleton, GenericInventoryItemService>();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.MapOpenApi();
+}
+
+app.UseHttpsRedirection();
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
\ No newline at end of file
diff --git a/WyvernInventory.API/Properties/launchSettings.json b/WyvernInventory.API/Properties/launchSettings.json
new file mode 100644
index 0000000..2a3e901
--- /dev/null
+++ b/WyvernInventory.API/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://0.0.0.0:5244",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "https://localhost:7048;http://localhost:5244",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/WyvernInventory.API/WeatherForecast.cs b/WyvernInventory.API/WeatherForecast.cs
new file mode 100644
index 0000000..15ccecf
--- /dev/null
+++ b/WyvernInventory.API/WeatherForecast.cs
@@ -0,0 +1,12 @@
+namespace WyvernInventory.API;
+
+public class WeatherForecast
+{
+ public DateOnly Date { get; set; }
+
+ public int TemperatureC { get; set; }
+
+ public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
+
+ public string? Summary { get; set; }
+}
\ No newline at end of file
diff --git a/WyvernInventory.API/WyvernInventory.API.csproj b/WyvernInventory.API/WyvernInventory.API.csproj
new file mode 100644
index 0000000..c010abe
--- /dev/null
+++ b/WyvernInventory.API/WyvernInventory.API.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net10.0
+ enable
+ enable
+ Linux
+
+
+
+
+
+
+
+
+ .dockerignore
+
+
+
+
+
+
+
+
+
diff --git a/WyvernInventory.API/WyvernInventory.API.http b/WyvernInventory.API/WyvernInventory.API.http
new file mode 100644
index 0000000..7880a67
--- /dev/null
+++ b/WyvernInventory.API/WyvernInventory.API.http
@@ -0,0 +1,25 @@
+@WyvernInventory.API_HostAddress = http://localhost:5244
+
+POST http://localhost:5244/inventory
+Content-Type: application/json
+
+[
+ {
+ "name": "RTX 4070"
+ }
+]
+###
+
+POST http://localhost:5244/inventory/upsert
+Content-Type: application/json
+
+[
+ {
+ "name": "RTX 4070 Ti Super",
+ "description": "Desktop GPU"
+ },
+ {
+ "name": "Ryzen 9 7950X3D",
+ "description": "Desktop CPU 16c/32t"
+ }
+]
\ No newline at end of file
diff --git a/WyvernInventory.API/appsettings.Development.json b/WyvernInventory.API/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/WyvernInventory.API/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/WyvernInventory.API/appsettings.json b/WyvernInventory.API/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/WyvernInventory.API/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/WyvernInventory.Core/Interfaces/Repos/IDbObjectRepo.cs b/WyvernInventory.Core/Interfaces/Repos/IDbObjectRepo.cs
new file mode 100644
index 0000000..ae56c81
--- /dev/null
+++ b/WyvernInventory.Core/Interfaces/Repos/IDbObjectRepo.cs
@@ -0,0 +1,8 @@
+namespace WyvernInventory.Core.Interfaces.Repos;
+
+public interface IDbObjectRepo
+{
+ public Task> GetAsync(Predicate? filter = null);
+ public Task<(int created, int updated)> UpsertAsync(List items);
+ public Task DeleteAsync(List items);
+}
\ No newline at end of file
diff --git a/WyvernInventory.Core/Interfaces/Services/IDataObjectService.cs b/WyvernInventory.Core/Interfaces/Services/IDataObjectService.cs
new file mode 100644
index 0000000..770338f
--- /dev/null
+++ b/WyvernInventory.Core/Interfaces/Services/IDataObjectService.cs
@@ -0,0 +1,8 @@
+namespace WyvernInventory.Core.Interfaces.Services;
+
+public interface IDataObjectService
+{
+ public Task> GetAsync(Predicate? filter = null);
+ public Task<(int created, int updated)> UpsertAsync(List items);
+ public Task DeleteAsync(List items);
+}
\ No newline at end of file
diff --git a/WyvernInventory.Core/Models/Computer.cs b/WyvernInventory.Core/Models/Computer.cs
new file mode 100644
index 0000000..d2d9d3e
--- /dev/null
+++ b/WyvernInventory.Core/Models/Computer.cs
@@ -0,0 +1,6 @@
+namespace WyvernInventory.Core.Models;
+
+public record Computer() : GenericInventoryItem
+{
+
+}
\ No newline at end of file
diff --git a/WyvernInventory.Core/Models/Cpu.cs b/WyvernInventory.Core/Models/Cpu.cs
new file mode 100644
index 0000000..80ff5d4
--- /dev/null
+++ b/WyvernInventory.Core/Models/Cpu.cs
@@ -0,0 +1,6 @@
+namespace WyvernInventory.Core.Models;
+
+public record Cpu() : GenericInventoryItem
+{
+
+}
\ No newline at end of file
diff --git a/WyvernInventory.Core/Models/GenericInventoryItem.cs b/WyvernInventory.Core/Models/GenericInventoryItem.cs
new file mode 100644
index 0000000..a4126c0
--- /dev/null
+++ b/WyvernInventory.Core/Models/GenericInventoryItem.cs
@@ -0,0 +1,8 @@
+namespace WyvernInventory.Core.Models;
+
+public record GenericInventoryItem()
+{
+ public int? Id { get; set; }
+ public string? Name { get; set; }
+ public string? Description { get; set; }
+}
\ No newline at end of file
diff --git a/WyvernInventory.Core/Models/Gpu.cs b/WyvernInventory.Core/Models/Gpu.cs
new file mode 100644
index 0000000..6596505
--- /dev/null
+++ b/WyvernInventory.Core/Models/Gpu.cs
@@ -0,0 +1,6 @@
+namespace WyvernInventory.Core.Models;
+
+public record Gpu() : GenericInventoryItem
+{
+
+}
\ No newline at end of file
diff --git a/WyvernInventory.Core/Models/Router.cs b/WyvernInventory.Core/Models/Router.cs
new file mode 100644
index 0000000..3bd709c
--- /dev/null
+++ b/WyvernInventory.Core/Models/Router.cs
@@ -0,0 +1,6 @@
+namespace WyvernInventory.Core.Models;
+
+public record Router() : GenericInventoryItem
+{
+
+}
\ No newline at end of file
diff --git a/WyvernInventory.Core/Models/StorageDrive.cs b/WyvernInventory.Core/Models/StorageDrive.cs
new file mode 100644
index 0000000..abb210a
--- /dev/null
+++ b/WyvernInventory.Core/Models/StorageDrive.cs
@@ -0,0 +1,6 @@
+namespace WyvernInventory.Core.Models;
+
+public record StorageDrive() : GenericInventoryItem
+{
+
+}
\ No newline at end of file
diff --git a/WyvernInventory.Core/Models/Switch.cs b/WyvernInventory.Core/Models/Switch.cs
new file mode 100644
index 0000000..e7f7af6
--- /dev/null
+++ b/WyvernInventory.Core/Models/Switch.cs
@@ -0,0 +1,6 @@
+namespace WyvernInventory.Core.Models;
+
+public record Switch() : GenericInventoryItem
+{
+
+}
\ No newline at end of file
diff --git a/WyvernInventory.Core/WyvernInventory.Core.csproj b/WyvernInventory.Core/WyvernInventory.Core.csproj
new file mode 100644
index 0000000..81e5926
--- /dev/null
+++ b/WyvernInventory.Core/WyvernInventory.Core.csproj
@@ -0,0 +1,11 @@
+
+
+
+ net10.0
+ enable
+ enable
+ WyvernInventory.Core
+ WyvernInventory.Core
+
+
+
diff --git a/WyvernInventory.Infrastructure/Repos/GenericInventoryItemRepo.cs b/WyvernInventory.Infrastructure/Repos/GenericInventoryItemRepo.cs
new file mode 100644
index 0000000..428182a
--- /dev/null
+++ b/WyvernInventory.Infrastructure/Repos/GenericInventoryItemRepo.cs
@@ -0,0 +1,53 @@
+using WyvernInventory.Core.Interfaces.Repos;
+using WyvernInventory.Core.Models;
+
+namespace WyvernInventory.Infrastructure.Repos;
+
+public class GenericInventoryItemRepo : IDbObjectRepo
+{
+ private List _genericItemList = [];
+
+ public async Task> GetAsync(Predicate? filter = null)
+ {
+ List result = [];
+
+ if (filter is null) return _genericItemList;
+
+ result.AddRange(_genericItemList.Where(item => filter(item)));
+
+ return result;
+ }
+
+ public async Task<(int created, int updated)> UpsertAsync(List items)
+ {
+ int created = 0;
+ int updated = 0;
+ foreach (var item in items)
+ {
+ if (item.Id is not null && _genericItemList.Any(_ => _.Id == item.Id))
+ {
+ int index = _genericItemList.IndexOf(_genericItemList.Find(_ => _.Id == item.Id));
+ _genericItemList[index] = item;
+
+ updated++;
+ }
+ else
+ {
+ item.Id = _genericItemList.Count;
+ _genericItemList.Add(item);
+
+ created++;
+ }
+ }
+
+ return (created, updated);
+ }
+
+ public async Task DeleteAsync(List items)
+ {
+ foreach (var item in items)
+ {
+ _genericItemList.Remove(_genericItemList.Find(_ => _.Id == item.Id));
+ }
+ }
+}
\ No newline at end of file
diff --git a/WyvernInventory.Infrastructure/Services/GenericInventoryItemService.cs b/WyvernInventory.Infrastructure/Services/GenericInventoryItemService.cs
new file mode 100644
index 0000000..a151f53
--- /dev/null
+++ b/WyvernInventory.Infrastructure/Services/GenericInventoryItemService.cs
@@ -0,0 +1,17 @@
+using WyvernInventory.Core.Interfaces.Repos;
+using WyvernInventory.Core.Interfaces.Services;
+using WyvernInventory.Core.Models;
+
+namespace WyvernInventory.Infrastructure.Services;
+
+public class GenericInventoryItemService(IDbObjectRepo dbRepo)
+ : IDataObjectService
+{
+ private readonly IDbObjectRepo _dbRepo = dbRepo;
+
+ public async Task> GetAsync(Predicate? filter = null) => await _dbRepo.GetAsync(filter);
+
+ public async Task<(int created, int updated)> UpsertAsync(List items) => await _dbRepo.UpsertAsync(items);
+
+ public async Task DeleteAsync(List items) => await _dbRepo.DeleteAsync(items);
+}
\ No newline at end of file
diff --git a/WyvernInventory.Infrastructure/WyvernInventory.Infrastructure.csproj b/WyvernInventory.Infrastructure/WyvernInventory.Infrastructure.csproj
new file mode 100644
index 0000000..dfc73b9
--- /dev/null
+++ b/WyvernInventory.Infrastructure/WyvernInventory.Infrastructure.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/WyvernInventory.sln b/WyvernInventory.sln
new file mode 100644
index 0000000..7095180
--- /dev/null
+++ b/WyvernInventory.sln
@@ -0,0 +1,33 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WyvernInventory.API", "WyvernInventory.API\WyvernInventory.API.csproj", "{FDA9B08A-4443-4E6F-A3A4-C2B7E4650E90}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CE4BF34F-D890-4DCD-BF09-5CAC9C7BD706}"
+ ProjectSection(SolutionItems) = preProject
+ compose.yaml = compose.yaml
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WyvernInventory.Core", "WyvernInventory.Core\WyvernInventory.Core.csproj", "{CE9A8DB9-0746-4653-BBB1-27AC278C79AE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WyvernInventory.Infrastructure", "WyvernInventory.Infrastructure\WyvernInventory.Infrastructure.csproj", "{D45E7747-B12D-4922-B1F8-75D6130D8ACF}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {FDA9B08A-4443-4E6F-A3A4-C2B7E4650E90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FDA9B08A-4443-4E6F-A3A4-C2B7E4650E90}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FDA9B08A-4443-4E6F-A3A4-C2B7E4650E90}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FDA9B08A-4443-4E6F-A3A4-C2B7E4650E90}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CE9A8DB9-0746-4653-BBB1-27AC278C79AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CE9A8DB9-0746-4653-BBB1-27AC278C79AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CE9A8DB9-0746-4653-BBB1-27AC278C79AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CE9A8DB9-0746-4653-BBB1-27AC278C79AE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D45E7747-B12D-4922-B1F8-75D6130D8ACF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D45E7747-B12D-4922-B1F8-75D6130D8ACF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D45E7747-B12D-4922-B1F8-75D6130D8ACF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D45E7747-B12D-4922-B1F8-75D6130D8ACF}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000..0df10ad
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,7 @@
+services:
+ wyverninventory.api:
+ image: wyverninventory.api
+ build:
+ context: .
+ dockerfile: WyvernInventory.API/Dockerfile
+