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
17 changes: 17 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
**/bin/
**/obj/
**/.vs/
**/.vscode/
**/*.user
**/*.suo
**/*.pdb
**/*.db
**/*.log
**/node_modules/
Dockerfile*
docker-compose*
.env
.git
.gitignore
README.md
/vsdbg
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
MSSQL_SA_PASSWORD=YourStrong!Passw0rd
MONGO_INITDB_ROOT_USERNAME=admin
MONGO_INITDB_ROOT_PASSWORD=secret
RABBITMQ_DEFAULT_USER=guest
RABBITMQ_DEFAULT_PASS=guest
48 changes: 48 additions & 0 deletions .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Parallel Docker Builds with Cache

on:
push:
branches: [develop, master]
pull_request:
branches: [develop, master]

jobs:
build-docker:
name: "Build docker images"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- dockerfile: OrderTrackingApp.Api/Dockerfile
image-name: ordertrackingapp-api
- dockerfile: OrderTrackingApp.Consumer/Dockerfile
image-name: ordertrackingapp-consumer
- dockerfile: OrderTrackingApp.Blazor.Server/Dockerfile
image-name: ordertrackingapp-blazor-server
- dockerfile: OrderTrackingApp.MigrationRunner/Dockerfile
image-name: ordertrackingapp-migration-runner

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ matrix.image-name }}
restore-keys: |
${{ runner.os }}-buildx-

- name: Build Docker image with cache
run: |
docker buildx build \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-f ${{ matrix.dockerfile }} \
-t ${{ matrix.image-name }} \
--load .
12 changes: 12 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [

{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}
12 changes: 12 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "dotnet",
"task": "build",
"group": "build",
"problemMatcher": [],
"label": "dotnet: build"
}
]
}
36 changes: 36 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- Use PackageVersion to define versions centrally -->
<PackageVersion Include="AutoMapper" Version="15.0.1" />
<PackageVersion Include="FluentValidation" Version="12.0.0" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="12.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageVersion Include="Scalar.AspNetCore" Version="2.6.3" />
<PackageVersion Include="Swashbuckle.AspNetCore.Filters" Version="8.0.3" />
<PackageVersion Include="MediatR" Version="13.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
<PackageVersion Include="RabbitMQ.Client" Version="7.1.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.0" />
<PackageVersion Include="MongoDB.Driver" Version="3.4.0" />

<!-- Test packages often need specific IncludeAssets/PrivateAssets metadata which can be defined here -->
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="xunit" Version="2.5.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.3" />

<!-- The EF Core Design tools package needs specific metadata -->
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
</ItemGroup>
</Project>
44 changes: 44 additions & 0 deletions OrderTrackingApp.Api/Controllers/OrdersController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using AutoMapper;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using OrderTrackingApp.Api.Examples;
using OrderTrackingApp.Api.Models;
using OrderTrackingApp.Application.Orders.Commands;
using OrderTrackingApp.Application.Orders.Queries;
using Swashbuckle.AspNetCore.Filters;

namespace OrderTrackingApp.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class OrdersController(IMediator mediator, IMapper mapper) : ControllerBase
{
[HttpPost]

[SwaggerRequestExample(typeof(CreateOrderRequest), typeof(CreateOrderRequestExample))]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderRequest request)
{
var command = mapper.Map<CreateOrderCommand>(request);
var orderId = await mediator.Send(command);

return CreatedAtAction(nameof(CreateOrder), new { id = orderId }, new { OrderId = orderId });
}

[HttpGet("{id}")]
public async Task<IActionResult> GetOrderById(Guid id)
{
var order = await mediator.Send(new GetOrderByIdQuery(id));
return Ok(order);
}

[HttpGet]
public async Task<IActionResult> GetOrders([FromQuery] GetOrdersRequest request)
{
var query = mapper.Map<GetOrdersQuery>(request);

var orders = await mediator.Send(query);
return Ok(orders);
}
}
}
40 changes: 40 additions & 0 deletions OrderTrackingApp.Api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# Copy root package props first
COPY ["Directory.Packages.props", "."]

COPY ["OrderTrackingApp.Api/OrderTrackingApp.Api.csproj", "OrderTrackingApp.Api/"]
COPY ["OrderTrackingApp.Application/OrderTrackingApp.Application.csproj", "OrderTrackingApp.Application/"]
COPY ["OrderTrackingApp.Application.Contracts/OrderTrackingApp.Application.Contracts.csproj", "OrderTrackingApp.Application.Contracts/"]
COPY ["OrderTrackingApp.Domain/OrderTrackingApp.Domain.csproj", "OrderTrackingApp.Domain/"]
COPY ["OrderTrackingApp.Infrastructure/OrderTrackingApp.Infrastructure.csproj", "OrderTrackingApp.Infrastructure/"]
COPY ["OrderTrackingApp.Persistence/OrderTrackingApp.Persistence.csproj", "OrderTrackingApp.Persistence/"]
COPY ["OrderTrackingApp.ReadPersistence/OrderTrackingApp.ReadPersistence.csproj", "OrderTrackingApp.ReadPersistence/"]

# Copy your root certificate into the container
COPY nscacert.crt /usr/local/share/ca-certificates/nscacert.crt

# Update the certificate store
RUN update-ca-certificates

RUN dotnet restore "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj"

COPY . .

RUN dotnet build "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" -c Release -o /app/build --no-restore

FROM build AS publish
RUN dotnet publish "OrderTrackingApp.Api/OrderTrackingApp.Api.csproj" -c Release -o /app/publish --no-restore

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

ENV ASPNETCORE_URLS=http://0.0.0.0:8080

ENTRYPOINT ["dotnet", "OrderTrackingApp.Api.dll"]
28 changes: 28 additions & 0 deletions OrderTrackingApp.Api/Examples/CreateOrderRequestExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using OrderTrackingApp.Api.Models;
using Swashbuckle.AspNetCore.Filters;

namespace OrderTrackingApp.Api.Examples;

public class CreateOrderRequestExample : IExamplesProvider<CreateOrderRequest>
{
public CreateOrderRequest GetExamples()
{
return new CreateOrderRequest
{
CustomerId = Guid.NewGuid(),
Items = new List<CreateOrderItemRequest>
{
new CreateOrderItemRequest
{
ProductId = Guid.Parse("b111a4dd-f45c-4d40-a6a3-3002f62f823f"),
Quantity = 2
},
new CreateOrderItemRequest
{
ProductId = Guid.Parse("2a13551b-059c-4035-b275-8a8cb82bd272"),
Quantity = 3
}
}
};
}
}
34 changes: 34 additions & 0 deletions OrderTrackingApp.Api/Extensions/ApiServiceRegistrations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using FluentValidation;
using OrderTrackingApp.Api.Examples;
using OrderTrackingApp.Api.MappingProfiles;
using OrderTrackingApp.Api.Validators;
using Swashbuckle.AspNetCore.Filters;
using System.Text.Json;

namespace OrderTrackingApp.Api.Extensions
{
public static class ApiServiceRegistrations
{
public static IServiceCollection AddApiServices(this IServiceCollection services)
{
services.AddEndpointsApiExplorer();

services.AddSwaggerGen(options => {
options.ExampleFilters();
});

services.AddSwaggerExamplesFromAssemblyOf<CreateOrderRequestExample>();

services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});

services.AddValidatorsFromAssemblyContaining<CreateOrderRequestValidator>();
services.AddAutoMapper(config => { }, typeof(OrderProfile).Assembly);

return services;
}
}
}
18 changes: 18 additions & 0 deletions OrderTrackingApp.Api/MappingProfiles/OrderProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using AutoMapper;
using OrderTrackingApp.Api.Models;
using OrderTrackingApp.Application.Orders.Commands;
using OrderTrackingApp.Application.Orders.Queries;

namespace OrderTrackingApp.Api.MappingProfiles
{
public class OrderProfile : Profile
{
public OrderProfile()
{
CreateMap<CreateOrderRequest, CreateOrderCommand>();
CreateMap<CreateOrderItemRequest, CreateOrderItemDto>();

CreateMap<GetOrdersRequest, GetOrdersQuery>();
}
}
}
22 changes: 22 additions & 0 deletions OrderTrackingApp.Api/Models/CreateOrderRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;

namespace OrderTrackingApp.Api.Models
{
public class CreateOrderRequest
{
[Required]
public Guid CustomerId { get; set; }

[Required]
public List<CreateOrderItemRequest> Items { get; set; } = [];
}

public class CreateOrderItemRequest
{
[Required]
public Guid ProductId { get; set; }

[Range(1, int.MaxValue)]
public int Quantity { get; set; }
}
}
15 changes: 15 additions & 0 deletions OrderTrackingApp.Api/Models/GetOrdersRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace OrderTrackingApp.Api.Models
{
public class GetOrdersRequest
{
public int Page { get; set; } = 1;

public int PageSize { get; set; } = 50;

public string? Status { get; set; }

public DateTime? FromDate { get; set; }

public DateTime? ToDate { get; set; }
}
}
27 changes: 27 additions & 0 deletions OrderTrackingApp.Api/OrderTrackingApp.Api.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>fd420179-d5b0-44df-b29b-018fbfbdb9f1</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\OrderTrackingApp</DockerfileContext>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" />
<PackageReference Include="FluentValidation" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Scalar.AspNetCore" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\OrderTrackingApp.Infrastructure\OrderTrackingApp.Infrastructure.csproj" />
<ProjectReference Include="..\OrderTrackingApp.Persistence\OrderTrackingApp.Persistence.csproj" />
<ProjectReference Include="..\OrderTrackingApp.ReadPersistence\OrderTrackingApp.ReadPersistence.csproj" />
</ItemGroup>

</Project>
Loading