Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,18 @@ dotnet_diagnostic.IDE0090.severity = none
dotnet_diagnostic.IDE0290.severity = none
dotnet_diagnostic.CA2000.severity = warning
dotnet_diagnostic.CA2013.severity = warning
dotnet_diagnostic.CA2016.severity = suggestion
dotnet_diagnostic.CA2017.severity = warning
dotnet_diagnostic.CA2213.severity = warning
dotnet_diagnostic.CA2215.severity = warning
dotnet_diagnostic.CA2227.severity = warning
dotnet_diagnostic.CA2300.severity = error
dotnet_diagnostic.CA2315.severity = error
dotnet_diagnostic.CA2321.severity = error
dotnet_diagnostic.CA5358.severity = error
dotnet_diagnostic.CA5404.severity = suggestion
dotnet_diagnostic.CA5405.severity = suggestion
csharp_style_prefer_top_level_statements = true:silent
dotnet_diagnostic.CA2016.severity = suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ jobs:
dotnet build src/WarehouseEngine.Infrastructure/WarehouseEngine.Infrastructure.csproj --configuration Release --no-restore
dotnet build src/WarehouseEngine.Api/WarehouseEngine.Api.csproj --configuration Release --no-restore
dotnet build tests/WarehouseEngine.Domain.Tests/WarehouseEngine.Domain.Tests.csproj --configuration Release --no-restore
dotnet build tests/WarehouseEngine.Infrastructure.Tests/WarehouseEngine.Infrastructure.Tests.csproj --configuration Release --no-restore
dotnet build tests/WarehouseEngine.Application.Tests/WarehouseEngine.Application.Tests.csproj --configuration Release --no-restore
dotnet build tests/WarehouseEngine.Api.Integration.Tests/WarehouseEngine.Api.Integration.Tests.csproj --configuration Release --no-restore

- name: Run Domain tests
run: dotnet test tests/WarehouseEngine.Domain.Tests/WarehouseEngine.Domain.Tests.csproj --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage"

- name: Run Infrastructure tests (with SQL Server)
run: dotnet test tests/WarehouseEngine.Infrastructure.Tests/WarehouseEngine.Infrastructure.Tests.csproj --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage"
run: dotnet test tests/WarehouseEngine.Application.Tests/WarehouseEngine.Application.Tests.csproj --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage"
env:
ConnectionStrings__DefaultConnection: "Server=localhost,1433;Database=WarehouseEngineTest;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=true;"
continue-on-error: true # Allow this to fail gracefully if it can't connect to SQL Server
Expand Down
26 changes: 20 additions & 6 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Markdown File

# Warehouse Engine

[Database-first](https://www.entityframeworktutorial.net/efcore/create-model-for-existing-database-in-ef-core.aspx) solutions assume the existence of a database.
This is not a production ready application. This is an example of a Warehouse Engine built using .NET 8, EF Core, and Clean Architecture principles.

This is useful when trying to build an application based off of an existing database, for example, when rewriting an application or when performance is crucial, etc.
## Database Deployment Strategy

This application uses Entity Framework Core (EF Core) for database management. The database is created and updated using EF Core migrations. The startup project and the data project are the same project in this case.

You can get started by adding an appsettings.local.json file to the WarehouseEngine.Infrastructure project with the following content with your connection string:

Please deploy this the database project of your choice then place the following in a appsettings.local.json file inside the [Api](WarehouseEngine.Api) project.
```
{
"ConnectionStrings": {
Expand All @@ -13,6 +17,16 @@ Please deploy this the database project of your choice then place the following
}
```

Because we use EF Core, a repo layer does not facilitate testing since we can test against MSSQLLOCALDB by creating a real instance of the db.
## Testing Strategy

### WarehouseEngine.Api.Integration.Tests

Integration tests against a real instance of the API using TestContainers for spinning up dependent services.

### Warehouse.Domain.Tests

Unit tests against the domain layer including entities, value objects, and domain services. Focuses on making sure they serialize correctly. TODO: this may not be needed anymore since we have moved to using DTOs.

### Warehouse.Application.Tests

Additionally, when we write integration tests, we can test against a real db instance which is spun up via TestContainers when the test begins.
Integration tests against the application and infrastructure layers using TestContainers for spinning up a SQL Server database. This tests business logic and data access logic such as querying data and turning them to DTOs.
15 changes: 3 additions & 12 deletions WarehouseEngine.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
# Visual Studio Version 18
VisualStudioVersion = 18.0.11005.162
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WarehouseEngine.Domain", "src\WarehouseEngine.Domain\WarehouseEngine.Domain.csproj", "{14C6CC92-D6CD-4C5E-98B6-AB37D0D9373D}"
EndProject
Expand All @@ -19,7 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WarehouseEngine.Application", "src\WarehouseEngine.Application\WarehouseEngine.Application.csproj", "{EB60C368-B903-419E-8A41-AC832A70F750}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WarehouseEngine.Infrastructure.Tests", "tests\WarehouseEngine.Infrastructure.Tests\WarehouseEngine.Infrastructure.Tests.csproj", "{4B54FE5B-FAB7-42DD-BC73-A5094569C84B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WarehouseEngine.Application.Tests", "tests\WarehouseEngine.Application.Tests\WarehouseEngine.Application.Tests.csproj", "{4B54FE5B-FAB7-42DD-BC73-A5094569C84B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WarehouseEngine.Api", "src\WarehouseEngine.Api\WarehouseEngine.Api.csproj", "{9298CA01-BEC1-4883-A796-38BC88924253}"
EndProject
Expand All @@ -33,8 +33,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{595DB01B
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WarehouseEngine.Api.Integration.Tests", "tests\WarehouseEngine.Api.Integration.Tests\WarehouseEngine.Api.Integration.Tests.csproj", "{16515E80-22E0-4C83-AE37-A097BA7F2C50}"
EndProject
Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "WarehouseEngine.Database", "src\WarehouseEngine.Database\WarehouseEngine.Database.sqlproj", "{9FD1326C-240B-4B4F-A193-FB1FDE11B86C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -75,12 +73,6 @@ Global
{16515E80-22E0-4C83-AE37-A097BA7F2C50}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16515E80-22E0-4C83-AE37-A097BA7F2C50}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16515E80-22E0-4C83-AE37-A097BA7F2C50}.Release|Any CPU.Build.0 = Release|Any CPU
{9FD1326C-240B-4B4F-A193-FB1FDE11B86C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9FD1326C-240B-4B4F-A193-FB1FDE11B86C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9FD1326C-240B-4B4F-A193-FB1FDE11B86C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{9FD1326C-240B-4B4F-A193-FB1FDE11B86C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9FD1326C-240B-4B4F-A193-FB1FDE11B86C}.Release|Any CPU.Build.0 = Release|Any CPU
{9FD1326C-240B-4B4F-A193-FB1FDE11B86C}.Release|Any CPU.Deploy.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -94,7 +86,6 @@ Global
{000AE932-71A3-470A-BFE2-8DA4D6A0E502} = {595DB01B-82D0-44E8-87E3-B94A57AE5199}
{98A17F17-FA68-4C10-BC2F-54CC71A083CC} = {E1CE43DA-50A7-426C-9DC0-2F3E2D3D4394}
{16515E80-22E0-4C83-AE37-A097BA7F2C50} = {595DB01B-82D0-44E8-87E3-B94A57AE5199}
{9FD1326C-240B-4B4F-A193-FB1FDE11B86C} = {E1CE43DA-50A7-426C-9DC0-2F3E2D3D4394}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {119463A0-5D13-4114-871C-01CB47F32500}
Expand Down
5 changes: 3 additions & 2 deletions src/WarehouseEngine.Api/Controllers/AuthenticateController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using WarehouseEngine.Application.Dtos;
using WarehouseEngine.Application.Interfaces;
using WarehouseEngine.Domain.Models.Auth;

Expand All @@ -29,7 +30,7 @@ public AuthenticateController(

[HttpPost]
[AllowAnonymous]
[ProducesResponseType(200)]
[ProducesResponseType(typeof(AuthenticationResponse), 200)]
[ProducesResponseType(401)]
[ProducesResponseType(500)]
public async Task<IActionResult> Login([FromBody] Login model)
Expand All @@ -53,7 +54,7 @@ public async Task<IActionResult> Login([FromBody] Login model)
string token = _jwtService.GetNewToken(authClaims);
Response.Headers.Append("Bearer", token);

return Ok();
return Ok(new AuthenticationResponse(token));
}
return Unauthorized();
}
Expand Down
4 changes: 3 additions & 1 deletion src/WarehouseEngine.Api/Controllers/CustomerController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WarehouseEngine.Api.Extensions.ErrorTypeExtensions;
using WarehouseEngine.Application.Dtos;
using WarehouseEngine.Application.Interfaces;
using WarehouseEngine.Domain.Entities;

Expand All @@ -21,7 +22,7 @@ public CustomerController(ICustomerService customer, ILogger<CustomerController>
}

[HttpGet]
[ProducesResponseType(typeof(CustomerResponseDto), 200)]
[ProducesResponseType<CustomerResponseDto>(200)]
public async Task<ActionResult<CustomerResponseDto>> Get(Guid id)
{
var customer = await _customerService.GetByIdAsync(id);
Expand All @@ -38,6 +39,7 @@ public async Task<ActionResult<CustomerResponseDto>> Get(Guid id)
}

[HttpGet("count")]
[ProducesResponseType<int>(200)]
public async Task<ActionResult<int>> Count()
{
return Ok(await _customerService.GetCount());
Expand Down
1 change: 1 addition & 0 deletions src/WarehouseEngine.Api/Controllers/ItemController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WarehouseEngine.Application.Dtos;
using WarehouseEngine.Application.Interfaces;
using WarehouseEngine.Domain.Entities;

Expand Down
1 change: 1 addition & 0 deletions src/WarehouseEngine.Api/Controllers/VendorController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WarehouseEngine.Api.Extensions.ErrorTypeExtensions;
using WarehouseEngine.Application.Dtos;
using WarehouseEngine.Application.Interfaces;
using WarehouseEngine.Domain.Entities;

Expand Down
9 changes: 9 additions & 0 deletions src/WarehouseEngine.Api/Examples/AuthenticationExamples.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using WarehouseEngine.Application.Dtos;

namespace WarehouseEngine.Api.Examples;

public static class AuthenticationExamples
{
public static readonly AuthenticationResponse AuthenticationResultExample =
new AuthenticationResponse("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ");
}
2 changes: 1 addition & 1 deletion src/WarehouseEngine.Api/Examples/CustomerExamples.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using WarehouseEngine.Domain.Entities;
using WarehouseEngine.Application.Dtos;
using WarehouseEngine.Domain.ValueObjects;

namespace WarehouseEngine.Api.Examples;
Expand Down
3 changes: 2 additions & 1 deletion src/WarehouseEngine.Api/Examples/VendorExamples.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using WarehouseEngine.Domain.Entities;
using WarehouseEngine.Application.Dtos;
using WarehouseEngine.Domain.Entities;

namespace WarehouseEngine.Api.Examples;

Expand Down
10 changes: 6 additions & 4 deletions src/WarehouseEngine.Api/Examples/_ExampleDictionary.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using System.Text.Json;
using Microsoft.OpenApi.Any;
using System.Text.Json.Nodes;
using WarehouseEngine.Application.Dtos;
using WarehouseEngine.Domain.Entities;

namespace WarehouseEngine.Api.Examples;
public static class ExampleDictionary
{
public static readonly IReadOnlyDictionary<Type, OpenApiString> Examples = new Dictionary<Type, OpenApiString>
public static readonly IReadOnlyDictionary<Type, JsonNode?> Examples = new Dictionary<Type, JsonNode?>
{
{ typeof(CustomerResponseDto), new OpenApiString(JsonSerializer.Serialize(CustomerExamples.CustomerResponseDto, JsonSerializerOptions.Web))},
{ typeof(VendorResponseDto), new OpenApiString(JsonSerializer.Serialize(VendorExamples.VendorResponseDto, JsonSerializerOptions.Web))},
{ typeof(CustomerResponseDto), JsonSerializer.SerializeToNode(CustomerExamples.CustomerResponseDto, JsonSerializerOptions.Web) },
{ typeof(VendorResponseDto), JsonSerializer.SerializeToNode(VendorExamples.VendorResponseDto, JsonSerializerOptions.Web) },
{ typeof(AuthenticationResponse), JsonSerializer.SerializeToNode(AuthenticationExamples.AuthenticationResultExample, JsonSerializerOptions.Web) }
};
}
36 changes: 30 additions & 6 deletions src/WarehouseEngine.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi;
using Scalar.AspNetCore;
using WarehouseEngine.Api.Configuration;
using WarehouseEngine.Api.Examples;
Expand Down Expand Up @@ -91,12 +91,36 @@
});
services.AddOpenApi(options =>
{
options.AddOperationTransformer((operation, context, cancellationToken) =>
{
if (operation.Tags.Select(t => t.Name).Contains("Authenticate"))
options.AddDocumentTransformer((document, context, cancellationToken) => {
IOpenApiSecurityScheme jwtBearerScheme = new OpenApiSecurityScheme()
{
Type = SecuritySchemeType.Http,
BearerFormat = "Json Web Token",
In = ParameterLocation.Header,
Description = "Please insert JWT token into field",
Name = "Authorization",
Scheme = JwtBearerDefaults.AuthenticationScheme,
};

var securitySchemes = new Dictionary<string, IOpenApiSecurityScheme>
{
[JwtBearerDefaults.AuthenticationScheme] = jwtBearerScheme
};

document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes = securitySchemes;

// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/customize-openapi?view=aspnetcore-10.0#use-document-transformers
// Apply it as a requirement for all operations
foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations))

Check warning on line 115 in src/WarehouseEngine.Api/Program.cs

View workflow job for this annotation

GitHub Actions / api-build-test

Possible null reference return.

Check warning on line 115 in src/WarehouseEngine.Api/Program.cs

View workflow job for this annotation

GitHub Actions / api-build-test

Possible null reference return.

Check warning on line 115 in src/WarehouseEngine.Api/Program.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference return.

Check warning on line 115 in src/WarehouseEngine.Api/Program.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference return.
{
var authenticate200Response = operation.Responses.FirstOrDefault(x => x.Key == "200").Value;
authenticate200Response?.Headers.Add("Bearer", new OpenApiHeader() { Description = "Contains JWT" });
if (operation.Value.Tags is not null && operation.Value.Tags.Any(tag => tag.Name == "Authenticate")) { continue; }

operation.Value.Security ??= [];
operation.Value.Security.Add(new OpenApiSecurityRequirement
{
[new OpenApiSecuritySchemeReference("Bearer", document)] = []
});
}

return Task.CompletedTask;
Expand Down
7 changes: 4 additions & 3 deletions src/WarehouseEngine.Api/WarehouseEngine.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
<ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageReference Include="Microsoft.OpenApi" Version="2.3.10" />
<PackageReference Include="OneOf" Version="3.0.271" />
<PackageReference Include="Scalar.AspNetCore" Version="2.2.1" />
<PackageReference Include="Scalar.AspNetCore" Version="2.11.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace WarehouseEngine.Application.Dtos;

public record AuthenticationResponse(string Token);
Loading
Loading